網(wǎng)站首頁 編程語言 正文
基本概念
線性表是由 n(n≥0)個(gè)數(shù)據(jù)元素組成的有窮序列
大白話:在內(nèi)存上一個(gè)個(gè)排著,找到一個(gè),剩下的挨著找就行
數(shù)據(jù)元素又稱作結(jié)點(diǎn)
吐槽:人類在創(chuàng)造術(shù)語的路上就是這么帶勁,上節(jié)課剛說數(shù)據(jù)元素又稱元素,這又來一個(gè)結(jié)點(diǎn),得,記住吧
結(jié)點(diǎn)個(gè)數(shù)叫做表長,那么我們用一張完整的圖來說明一下
線性表的基本運(yùn)算,需要了解一下
- 初始化 Initiate(L)
- 求表長 Length(L)
- 讀表元素 Get(L,i)
- 定位 Locate(L,i)
- 插入 Insert(L,x,i)
- 刪除 Delete(L,i)
線性表的順序存儲(chǔ)
用順序存儲(chǔ)實(shí)現(xiàn)的線性表稱為順序表。一般使用數(shù)組來表示順序表
接下就是刺激的時(shí)刻了,比較難的部分來了,因?yàn)橐?C 來實(shí)現(xiàn)線性表的基本運(yùn)算
首先假定線性表的數(shù)據(jù)元素的類型為DataType
,這個(gè)DataType
可以是自定義的,也可以是默認(rèn)的int
,char
等類型
const int Maxsize = 100 ; // 預(yù)先定義一個(gè)足夠大的常數(shù) typedef struct{ DataType data[Maxsize]; // 存放數(shù)據(jù)的數(shù)組 int length ; // 順序表的實(shí)際長度 } SeqList; // 順序表類型名為SeqList SeqList L ; // 定義一個(gè)L為順序表
實(shí)現(xiàn)插入操作,函數(shù)名字為InsertSeqList(SeqList L,DataType x,int i)
表示在順序表第 i(1≤i≤n+1)個(gè)元素之前,插入一個(gè)新元素。使得線性表長度加 1。
上面是邏輯上的 C 語言實(shí)現(xiàn),接下來咱們先引用一個(gè)圖,說明一下如何用 C 語言在內(nèi)存上開辟一塊空間,并且向里面存數(shù)據(jù)
#include <stdio.h> #include <stdlib.h> const int Maxsize = 10; typedef struct SeqList{ int *data; //一個(gè)int指針,后面用來初始化數(shù)組用 int length; } seq; // 順序表的初始化函數(shù) seq init(){ seq s; s.data = (int*)malloc(Maxsize*sizeof(int)); // 構(gòu)造一個(gè)空的順序表,動(dòng)態(tài)申請(qǐng)存儲(chǔ)空間 if(!s.data) // 如果申請(qǐng)失敗,退出程序 { printf("初始化失敗"); exit(0); } s.length = 0; // 空表的長度初始化為0 return s; }
上述代碼,相當(dāng)于在內(nèi)存上做了圖示的操作
開辟空間之后,向每個(gè)小格子里面添加數(shù)字
void display(seq s){ for(int i=0;i<s.length;i++){ printf("%d",s.data[i]); } printf("\n"); } int main() { seq s = init(); //添加一個(gè)元素進(jìn)入 for(int i=1;i<=5;i++){ s.data[i-1] = i; s.length++; } printf("初始化之后,表的數(shù)據(jù)為:\n"); display(s); return 0; }
可以看動(dòng)畫理解
添加元素完成之后,就是刪除元素了
刪除的基本步驟
- 結(jié)點(diǎn) ai+1,....an依次向左移動(dòng)一個(gè)元素位置
- 表長度減 1
看一下代碼吧
seq delete_seq(seq s,int i){ if(i<1||i>s.length){ printf("位置錯(cuò)誤"); exit(0); } // 第i個(gè)元素下標(biāo)修改為i-1 for(int j=i;j<s.length;j++){ s.data[j-1] = s.data[j]; } s.length--; return s; }
接下來實(shí)現(xiàn)定位的算法,說白了,就是判斷一個(gè)值(x)的位置(i)
C 語言的代碼如下
// 注意,這個(gè)地方需要返回的為int了,也就是位置 int locate(seq s,int x){ int i =0; while((i<s.length)&&(s.data[i]!=x)){ i++; } if(i<s.length) return i+1; else return -1; }
線性表的順序存儲(chǔ)的時(shí)間復(fù)雜度
運(yùn)算 | 插入 | 刪除 | 定位 | 求表長 | 讀取元素 |
---|---|---|---|---|---|
時(shí)間復(fù)雜度 | O(n) | O(n) | O(n) | O(1) | O(1) |
具體是怎么來的,需要你自己看看算法的實(shí)現(xiàn)嘍,通過上述表格知道 順序表的插入、刪除算法在時(shí)間性能方面不是很理想,接下來我們就采用線性表的鏈接存儲(chǔ)來看一下,是否存在優(yōu)化。
線性表的鏈接存儲(chǔ)
鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),上來需要記住有三種常見的 單鏈表、循環(huán)鏈表、雙向循環(huán)鏈表
首先明確,單鏈表中每個(gè)結(jié)點(diǎn)由兩部分組成
- data 表示==數(shù)據(jù)域==
- next 表示==指針域==或==鏈域==
一些簡單的結(jié)點(diǎn)概念
線性表在單鏈表上實(shí)現(xiàn)基本運(yùn)算
接下來重頭戲來了,我們要用代碼實(shí)現(xiàn)一個(gè)簡單的單鏈表
空的單鏈表由一個(gè)頭指針和一個(gè)頭結(jié)點(diǎn)組成
初始化
初始化之前,我們需要先用 C 語言定義一個(gè)新的結(jié)構(gòu)體
//鏈表中的每個(gè)結(jié)點(diǎn)的實(shí)現(xiàn) //包含數(shù)據(jù)域與指針域 typedef struct node{ int data;// 數(shù)據(jù)域,為了計(jì)算方便,類型設(shè)置為數(shù)字 struct node *next; // 指針域,指向后繼元素 } Node,*LinkList;
結(jié)構(gòu)體定義好了之后,就可以開始初始化操作了 頭結(jié)點(diǎn)初始化其實(shí)就是在內(nèi)存上開辟一塊空間,然后將指針域指向 NULL
請(qǐng)看代碼,注意返回的是一個(gè)指針類型,說白了就是頭結(jié)點(diǎn)的地址
// 初始化 LinkList init(){ Node *L; // 定義一個(gè)頭結(jié)點(diǎn) L =(LinkList)malloc(sizeof(Node)); //頭結(jié)點(diǎn)申請(qǐng)地址 if(L == NULL){ printf("初始化失敗!\n"); exit(0); } L->next =NULL; return L; } 復(fù)制代碼
初始化成功,開始插入元素
插入元素,有頭插入、尾插、任意插
先說一下頭插,當(dāng)頭結(jié)點(diǎn)初始化完畢之后,第一個(gè)元素插入進(jìn)來就比較簡單了,看動(dòng)圖
這是插入一個(gè)元素,在用頭插法插入第二個(gè)元素呢?
新生成的 pnew2 首先將自己的指針域指向頭結(jié)點(diǎn)的指針域pnew2->next = L.next
,然后L.next = pnew2
即可
上述的邏輯寫成代碼如下
// 頭插入法 void insert_head(Node *L){ int i,n,num; // n表示元素的個(gè)數(shù) Node *pnew; printf("請(qǐng)輸入要插入的元素個(gè)數(shù):n = "); scanf("%d",&n); for(i=0;i<n;i++){ printf("請(qǐng)輸入第%d個(gè)元素: ",i+1); scanf("%d",&num); pnew = (LinkList)malloc(sizeof(Node)); pnew->data = num; // 將數(shù)字存儲(chǔ)到數(shù)據(jù)域 pnew->next = L->next; // 指針域指向L(頭結(jié)點(diǎn))的指針域 L->next = pnew; // 頭結(jié)點(diǎn)指針域指向新結(jié)點(diǎn)地址 } }
接下來看一下尾插法,其實(shí)理解起來也不難,說白了就是在鏈表后面追加元素即可 !
代碼如下,這個(gè)地方看一下里面有一個(gè)p=L
請(qǐng)問直接使用 L 可以嗎?為什么不直接用,搞清楚了,你也就明白了
// 尾插法 void insert_tail(Node *L){ int i,n,num; Node *p,*pnew; p = L; printf("要輸入元素的個(gè)數(shù):n = "); scanf("%d",&n); for(i=0;i<n;i++){ printf("請(qǐng)輸入第%d個(gè)元素:",i+1); scanf("%d",&num); pnew = (LinkList)malloc(sizeof(Node)); if(pnew == NULL){ printf("初始化失敗"); exit(0); } pnew->data = num; p->next = pnew; p = pnew; } p->next = NULL; }
剩下的算法實(shí)現(xiàn)就比較簡單了,例如求表長,通過循環(huán)的方式,計(jì)算一下即可
//求表長 int get_length(LinkList L){ LinkList p; int length = 0; p = L->next; // p 指向第一個(gè)結(jié)點(diǎn) while(p){ printf("單鏈表的數(shù)據(jù)為%d\n",p->data); length++; p = p->next; } return length; }
讀表中的元素
// 讀表中的元素 LinkList get_element(LinkList L,int i){ // 在單鏈表L中查找第i個(gè)結(jié)點(diǎn),若找到,則返回指向該結(jié)點(diǎn)的指針,否則返回NULL Node *p; p = L->next; int position = 1; while((position<i)&&(p!=NULL)){ // 當(dāng)未到第i結(jié)點(diǎn)且未到尾結(jié)點(diǎn)時(shí)繼續(xù)后移 p = p->next; position++; } if(i==position) return p; //找到第i個(gè)結(jié)點(diǎn) else return NULL; // 查找失敗 }
讀取表的元素,還可以按照值去找,返回位置,嘗試一下吧,寫起來都是比較容易的
int get_element_by_data(LinkList L,int x){ Node *p; p = L->next; int i = 0; while(p!=NULL && p->data == x){ p = p->next; i++; } if (p!=NULL) return i+1; else return 0; }
寫個(gè)復(fù)雜點(diǎn)的,在任意位置插入一個(gè)元素,這個(gè)還是好玩一些的
/在任意位置插入元素,x為要插入的內(nèi)容,i為插入的位置 void insert(LinkList L,int x,int i){ Node *p,*q; //p表示要插入的元素,q表示要被插入的元素 if(i==1) q = L; //如果i==1,那么p=L進(jìn)行初始化操作 else q = get_element(L,i-1); // 找到第i-1個(gè)元素 if(q==NULL){ printf("找不到位置"); exit(0); } else{ p =(LinkList)malloc(sizeof(Node)); p->data = x; p->next = q->next; // 新生成的p指向q的下一個(gè)結(jié)點(diǎn) q->next = p;//q的指針域指向新生成的p } }
簡單說明一下吧 大白話為 要在第 i 個(gè)位置插入一個(gè)元素 x,那么需要找到i-1
位置的元素,這個(gè)元素叫做 q
讓新元素p
(數(shù)據(jù)域?yàn)?x,指針域?yàn)榭眨┑闹羔樣蛑赶虻?code>i 元素,也就是q
原先的指針域,==防止丟失掉==
然后在叫q
的指針域指向p
的地址即可,如果還不明白,看圖
對(duì)于刪除任意位置的節(jié)點(diǎn),這個(gè)就要留給你自己了
如果將 ai移除鏈表,需要找到直接前驅(qū),讓直接前驅(qū)的指針域指向 ai+1的地址就可以了
記得,通過free(p)
釋放結(jié)點(diǎn)
刪除全部結(jié)點(diǎn)也需要自己完成一下,盡量把代碼寫完哦~~~
單鏈表的時(shí)間復(fù)雜度
- insert(LinkList L,int x,int i) 時(shí)間復(fù)雜度為 O(n^2^)
- 頭插法和尾插法時(shí)間復(fù)雜度為 O(n)
循環(huán)鏈表
環(huán)狀鏈表只需要將表中最后一個(gè)結(jié)點(diǎn)的指針指向頭結(jié)點(diǎn),鏈表就形成了一個(gè)環(huán) 如圖
循環(huán)鏈表如何想研究一下可以去實(shí)現(xiàn)約瑟夫環(huán),由于本教材中不是重點(diǎn),所以選修即可
雙向循環(huán)鏈表
雙向循環(huán)鏈表就是在單鏈表中的每個(gè)結(jié)點(diǎn)在增加一個(gè)指向直接前驅(qū)的指針域prior
,這樣每個(gè)結(jié)點(diǎn)就有兩個(gè)指針了
注意點(diǎn)
- 雙向循環(huán)鏈表是一種對(duì)稱結(jié)構(gòu),即可以直接訪問前驅(qū)結(jié)點(diǎn)又可以直接訪問后繼結(jié)點(diǎn),所以找前驅(qū)和后繼結(jié)點(diǎn)的時(shí)間復(fù)雜度都是 O(1),也可以得到結(jié)論雙向循環(huán)鏈表適合應(yīng)用在需要經(jīng)常查找結(jié)點(diǎn)的前驅(qū)和后繼場合
- p = p->prior->next = p->next->prior
教材中重點(diǎn)給出了刪除和插入的兩個(gè)邏輯,我們看一下
// p表示的是待刪除的結(jié)點(diǎn) p->prior->next = p->next; p->next->prior = p->prior; free(p)
圖示如下
大白話 先讓 p 等于要?jiǎng)h除的結(jié)點(diǎn),然后把 p 刪除前,需要將 p 的前驅(qū)和后繼結(jié)點(diǎn)連接起來,剛才的代碼干的就是這個(gè)事情!
插入邏輯
在 p 所指的結(jié)點(diǎn)后面插入一個(gè)新結(jié)點(diǎn)*t,需要修改四個(gè)指針:
t->prior = p; p->next = t; // 這兩個(gè)步驟將t和p連接起來了 t->next = p->next; p->next->prior = t; //這兩個(gè)步驟將t和p后繼結(jié)點(diǎn)連接起來了
期末考試
這章是期末考試或者自考的一個(gè)比較重要的考試章節(jié),一般會(huì)出現(xiàn)算法設(shè)計(jì)題,難度系數(shù)挺高的
建議,在能力范圍內(nèi)用 C 語言實(shí)現(xiàn)順序表的基本運(yùn)算,實(shí)現(xiàn)單鏈表的基本運(yùn)算
原文鏈接:https://juejin.cn/post/7147883328786399239
相關(guān)推薦
- 2022-09-19 C/C++最短路徑算法之迪杰斯特拉Dijkstra的實(shí)現(xiàn)詳解_C 語言
- 2022-07-02 element下拉框獲取選中的內(nèi)容
- 2023-01-15 解讀keras中的正則化(regularization)問題_python
- 2023-12-09 如何使用Python核對(duì)文件夾內(nèi)的文件
- 2022-01-13 封裝axios以及接口管理
- 2022-11-29 將VSCode添加至右鍵的菜單欄
- 2023-12-22 uni-app微信小程序分包
- 2022-12-13 C++?Boost實(shí)現(xiàn)數(shù)字與字符串轉(zhuǎn)化詳解_C 語言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支