深入學習Java檔案類File類(Demo詳解)
單鏈表
由於順序表的插入、刪除操作需要移動大量的元素,影響執行效率,由此引進了線性表的鏈式儲存。鏈式儲存線性表時,不需要使用地址連續的儲存單元,對線性表的插入刪除不需要移動元素,而只需要修改指標。
單鏈表
定義
每個連結串列結點,除了存放元素自身的資訊外,還需要存放一個指向後繼的指標。
typedef struct LNode{
ElemType data;
struct LNode* next;
}LNode, *LinkList; //LNode,LinkList 其實是一樣的,只是表示結點和連結串列更方便
初始化
不帶頭結點的單鏈表
bool InitLinklist(LinkList& L){
L = nullptr;
return true;
}
帶頭結點的單鏈表
bool InitLinkList(LinkList& L){
L = (Lnode*)malloc(sizeof(LNode)); //生成一個頭結點
if( L == nullptr){ //記憶體不足,分配失敗
return false;
}
L->next = nullptr; //頭結點之後暫時沒有任何結點
return true;
}
頭結點和頭指標的區別:
不管帶不帶頭結點,頭指標始終指向連結串列的第一個結點,而頭結點是帶頭結點的連結串列中的第一個結點,結點內通常不儲存資訊;不帶頭結點的單鏈表的頭結點就是連結串列地第一個結點,節點記憶體儲資訊。
引入頭結點後的優點:
1)由於頭結點的位置被存放在頭結點的指標域中,所以在連結串列的第一個位置上的操作和在連結串列的其他位置上的操作一致,無需進行特殊處理。
2)無論連結串列是否為空,其頭指標都指向頭結點的非空指標(空表中頭指標的指標域為空),因此空表和非空表的處理也就得到了統一。
建立單鏈表
頭插法
LinkList List_HeadInsert (LinkList& L,int n){
L = (LinkList)malloc(sizeof(LNode));
L->next = nullptr; //建立一個帶頭結點的空連結串列
for (int i = 0; i < n; i++) {
LNode* s = (LNode*)malloc(sizeof(LNode)); //新建一個結點
cin >> s->data;
s->next = L->next; //頭結點後插入新結點
L->next = s;
}
return L;
}
頭插法建立的連結串列時,讀入資料的順序與生成的連結串列資料是相反的,總的時間複雜度為O(n)。
尾插法
在尾插法的實現過程中,要建立一個連結串列尾指標,標註連結串列的最後一個節點,這樣就與新加入的結點聯絡起來,方便操作。
LinkList List_TailInsert(ListLink& L, int n){
L = (LinkList)malloc(sizeof(LNode));
L->next = nullptr;
LNode* tail = (LNode*)malloc(sizeof(LNode)); //生成表尾指標
tail = L; //表尾指標指向頭結點
for( int i = 0; i < n; i++){
LNode* s = (LNode*)malloc(sizeof(LNode)); //新建結點
cin >> s->data;
tail->next = s; //將尾指標的下一個結點指向新建結點
tail = s; //尾指標後移
}
tail->next = nullptr; //尾指標後繼設為空
reutrn L;
}
尾插法建立連結串列時,讀入資料的順序和生成的連結串列資料是相反的,中的時間複雜度為O(n)。
按序號查詢結點值
LNode* GetElem(LinkList L,int i){
int count = 1;
LNode* p = L->next; //指標p指向頭結點
if( i == 0){ //相當於返回頭結點
return L;
}
if( i < 1){
return nullptr; //i無效,返回空
}
while( p && count < i){
p = p->next;
count ++;
}
return p;
}
時間複雜度為O(n)。
按值查詢表結點
LNode* LocateElem(LinkList L, ElemType e){
LNode* p = L->next;
while( p != nullptr && p->data != e){
p = p->next;
}
return p;
}
時間複雜度為O(n)。
插入結點(後插)
bool ListLNode_Insert(LinkList& L, int i, ElemType e){
LNode* p = GetElem(L, i-1); //找到插入位置的前驅結點
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
該演算法主要的時間花費在尋找第i-1個結點上,時間複雜度為O(n),若在給定的結點後插入新結點,則時間複雜度為O(1)。
插入結點(前插)
前插操作:其實就是在該節點後面插入一個新結點,再將兩個結點的資料域交換,就輕鬆的完成了後插。
bool ListLNode_ForwardInsert(LinkList& L, int i, ElemType e){
LNode* p = GetElem(L,i); //找到第i個元素
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
swap(s->data,p->data);
return true;
}
刪除結點
bool ListLNode_Delete(LinkList& L, int i){
LNode* p = GeElem(L,i-1); //找到第i-1個元素,即*q的前驅結點
LNode* q = p->next;
p->next = q->next;
free(q);
return true;
}
刪除結點 *p
刪除結點 *p 的操作**可用刪除 p 的後繼結點操作來實現,實際上就是將後繼結點的值賦給 p ,然後刪除後繼結點。
bool ListLNode_Delete(LinkList& L, int i){
LNode* p = GeElem(L,i); //找到第i-1個元素,即*q的前驅結點
LNode* q = p->next;
p->data = q->data;
p->next = q->next;
free(q);
return true;
}
需要注意插入刪除時連結串列邊界情況的處理。
求表長
int LinkListLength(LinkList L){
LNode* p = L->next;
int len = 1;
while( p != nullptr){
p = p->next;
len ++;
}
return len;
}
顯示連結串列所有結點資訊
void List_Display(LinkList L){
LNode* p = L->next;
while( p != nullptr){
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
將單鏈表逆轉
第一種方法:雙指標法
建立兩個指標,cur和pre,pre 在右,cur在左。每次讓pre的next指標指向cur,完成一次區域性反轉,之後兩個指標同時向右移,直至連結串列尾。
void List_Reverse(LinkList& L){
LNode* cur = Nullptr; //最開始cur指向空
LNode* pre = L->next; //pre指向連結串列中第一個儲存資訊的結點
while( pre != nullptr ){
LNode* temp = pre->next;//建立臨時結點temp,提前儲存下一個結點的地址
pre->next = cur;
cur = pre;
pre = temp;
}
}
第二種方法:遞迴法
後續展開講解。。。
如果覺得本文對你有幫助的話,不妨關注作者一波,小小的關注其實對我很重要。更多高質量內容與資料請訪問:https://xiuxin.gitbook.io/datastructre/
如果喜歡的話,不妨關注一波,謝謝啦。