Data Structure in C ─ 鏈結串列
大綱 單向鏈結串列 環狀串列 雙向鏈結串列 鏈結串列的應用
鏈結串列 以陣列方式存放資料,若要插入(insert)或刪除(delete)某一節點(node)就備感困難 Ex. 陣列中已有a, b, d, e四個元素,若要將c插入d, e需往後一格 Ex.陣列中已有a, b, d, e四個元素,若要將d刪除為不浪費空間需挪移元素 利用鏈結串列(linked list) 解決問題
鏈結串列 (續) 陣列 vs. 鏈結陣列
單向鏈結串列 單向鏈結串列 (single linked list) 若單向串列中的每個節點(node)的資料結構有兩個欄位,分別為資料欄(data)及鏈結欄(next), ,若將節點定義為struct型態,為 struct node{ int data; struct node *next; }; struct node *head=NULL, *tail, *this, *prev, *x, *p, *ptr;
單向鏈結串列 (續) Ex. 串列A={a, b, c, d, e},以鏈結串列表示為 指向串列前端的指標 指向串列尾端的指標
單向鏈結串列 (續) 單向鏈結串列的加入動作 加入於串列的前端 加入於串列的尾端 加入在串列某一特定節點後面
加入於串列的前端 Step 1: x = (struct node *) malloc(sizeof(struct node)); x->next = NULL; Step 2: x->next = head;
加入於串列的尾端 Step 3: head = x; Step 1: x = (struct node *) malloc(sizeof(struct node)); x->next = NULL;
Step 2: tail->next = x; Step 3: tail = x; 若未用tail指標,則每次 都必須要追蹤串列的尾端
加入在串列某一特定節點後面 Assumption: 將一個節點x加在ptr所指節點後面 Step 1: x = (struct node *) malloc(sizeof(struct node)); x->next = NULL; Step 2: x->next = ptr->next;
Step 3: ptr->next = x;
void insert_node(struct node. ptr, struct node. head, struct node void insert_node(struct node *ptr, struct node *head, struct node *tail) { struct node *this if(head == NULL) /* 插入資料為第一筆 */ { ptr->next=NULL; head=ptr; tail=ptr;} else { this=head; if(ptr->key > this->key) /* 插入位置為前端 */ { ptr->next=this; head=ptr; }
else {while(this->next != NULL) { prev=this; this=this->next; if(ptr->key > this->key) /* 插入位置於中間 */ { ptr->next=this; prev->next=ptr; break; } } if(ptr->key <= this->score) /* 插入資料於尾端 */ { ptr->next=NULL; this->next=ptr; tail=ptr; }
單向鏈結串列 (續) 單向鏈結串列的刪除動作 刪除串列前端的節點 刪除串列尾端的節點 刪除串列某一特定節點
刪除串列前端的節點 Step 1: ptr = head; Step 2: head = ptr->next; Step 3: free(ptr);
刪除串列尾端的節點 ptr = head; Step 1:必須先追蹤tail的前一個節點 while (ptr->next != tail) ptr = ptr->next; Step 1:必須先追蹤tail的前一個節點 Step 2: ptr->next = NULL; Step 3: free(tail); Step 4: tail = ptr;
刪除串列某一特定節點 Step 1:尋找所要刪除的節點,並設定兩指標this及prev 即將被刪除的前一節點 即將被刪除節點 while (this->next != NULL) prev = this; this = this->next;
Step 2: prev->next = this->next; Step 3: free(this);
void delete_f(int del_key, struct node *head, struct node *tail) { struct node *clear, *this, *prev; this=head; if(del_key == head->key) { /* 刪除資料於前端 */ clear=this; if(head->next == NULL) /* 資料僅存在一筆 */ tail=NULL; head=head->next; free(clear); } while(this->next != NULL) { /* 刪除資料於中間 */ prev=this; this=this->next; if (del_key == this->key) { prev->next=this->next; free(clear); } } if(del_key == tail->key) { /*刪除資料於尾端 */ prev->next=NULL; tail=prev;
單向鏈結串列 (續) 將兩串列相接 三種情況 x串列為NULL,y串列有資料 x串列有資料,y串列為NULL x串列與y串列都有資料
void concatenate (struct node *x, struct node *y, struct node *y) { struct node *c; if (x = = NULL) z = y; else if (y = = NULL) z = x; else { z=x; c=x; while(c->next != NULL) c = c->next; c->next = y; }
將一串列反轉 串列的反轉是將原先的串列首變成串列尾 Ex. 若一串列原先以小排到大,此時若想由大到小排列 void invert(struct node *head) { struct node *this, *prev, *next_n; next_n = head; this =NULL; while(next_n != NULL){ prev = this; this = next_n; next_n = next->next; this->next = prev; } tail = head; head = this;
單向鏈結串列 (續) 計算串列的長度 串列長度指的是此串列共有多少個節點,計算上只要指標指到的為NULL,則利用一變數做累加,直到指標指到NULL為止 int length(struct node *head) { struct node *this; this = head; int leng=0; while (this != NULL) { leng++’ this = this->next;} return leng; }
環狀串列 假若將鏈結串列最後一個節點的指標指向第一個節點時,此串列稱為環狀串列(circular list) 可以由任一點來追蹤所有節點,而不必區分那一個是第一個節點
環狀串列 (續) 環狀串列的加入動作 加入於串列的前端 加入於串列的尾端 加入在串列某一特定節點後面 若串列為空串列(NULL) 若串列不為空串列 加入於串列的尾端 加入在串列某一特定節點後面
加入於串列的前端 ─ 串列為空串列 加入於串列的前端 ─ 串列不為空串列 Step 1: head = x; tail=x; Step 2: x->next = x; 加入於串列的前端 ─ 串列不為空串列 Step 1: x->next = head;
Step 2: tail->next = x; Step 3: head = x;
加入於串列的尾端 Step 2: x->next = head; Step 1: tail->next = x; Step 3: tail = x;
void insert_node(struct node. ptr, struct node. head, struct node void insert_node(struct node *ptr, struct node *head, struct node *tail) { struct node *prev, *this; if(head == NULL) { /* 插入資料為第一筆 */ ptr->next = ptr; head = ptr; tail = ptr; } else { this = head; if(ptr->key < this->key) { /* 插入位置為前端 */ ptr->next = this; tail->next = head; } else { while(this->next != head) { prev = this; this = this->next;
if(ptr->key < this->key) { /* 插入位置於中間 */ ptr->next = this; prev->next = ptr; break; } } if( ptr->key >= this->key) { /* 插入資料於尾端 */ ptr->next = head; this->next = ptr; tail = ptr; }
環狀串列 (續) 環狀串列的刪除動作 刪除串列前端的節點 刪除串列尾端的節點 刪除串列某一特定節點
刪除串列前端的節點 Step 1: tail->next = head->next; Step 2: ptr=head; head = head->next; Step 3: free(ptr);
刪除串列尾端的節點 ptr = head; Step 1: 必須先追蹤tail的前一個節點 while (ptr->next != tail) ptr = ptr->next; Step 1: 必須先追蹤tail的前一個節點 Step 2: ptr->next = hear; Step 3: free(tail); tail=ptr;
void delete_node(int del_key, struct node *head, struct node *tail) { struct node *clear, *prev, *this; this = head; if(del_key == head->key) { /* 刪除資料於前端 */ clear = head; if(head->next == head) /* 資料僅存在一筆 */ {head = NULL; tail = NULL;} else { head = head->next; tail->next = head; } }
while(this->next != head && head != NULL) /* 刪除資料於中間 */ { prev = this; this = this->next; if(del_key == ,this->key) { clear = this; prev->next = this->next; tail = prev; } } if(del_key == tail->key) { /*刪除資料於尾端 */ clear = tail; prev->next = head; free(clear);
將兩串列相接 Step 1: Atail->next = Bhead; Step 2: Btail->next = Ahead;
雙向鏈結串列 雙向鏈結串列(doubly linked list) 每個節點有三個欄位,一為左鏈結(LLink),二為資料(Data),三為右鏈結(RLink) 特點: 假設ptr是任何節點的指標,則 ptr = ptr->llink->rlink = ptr->rlink->llink; 若此雙向鏈結串列是空串列,則只有一個串列首
雙向鏈結串列 (續) 優點: 缺點: 加入或刪除時,無需知道其前一節點的位址 可以從任一節點找到其前一節點或後一節點 可以將某一節點遺失的左或右指標適時地加以恢復之 缺點: 增加一個指標空間 加入時需改變四個指標(單向只需改變兩個指標) 刪除時需改變兩個指標(單向只要改變一個指標)
雙向鏈結串列 (續) 雙向鏈結串列的加入動作 加入於串列的前端 加入於串列的尾端 加入在串列某一特定節點後面 假設head節點不放任何資料 void init_head(struct node *ptr, struct node *head, struct node *tail) { ptr = (struct node *) malloc (sizeof(struct node)); ptr->key = NULL; ptr->llink = ptr; ptr->rlink = ptr; head = ptr; tail = ptr;} 假設head節點不放任何資料
加入於串列的前端 Step 1: x->rlink = head; Step 2: x->llink = tail; Step 3: tail->rlink = x;
Step 4: head->rlink = x; Step 5: tail = x;
加入於串列的尾端 Assumption: 假設有一串列如下 Step 1: x->rlink = tail->rlink; Step 2: tail->rlink = x;
Step 3: x->llink = tail; Step 4: head->llink = x; Step 5: tail = x;
雙向鏈結串列 (續) 加入在串列某一特定節點後面 必須先搜尋到某特定的節點,並假設此串列是以key由小而大建立的,而搜尋步驟如下: prev = head; ptr = head->rlink; while(ptr != head && x->key <p->key) { prev = ptr; ptr = ptr->rlink; }
void insert_node(struct node. ptr, struct node. head, struct node void insert_node(struct node *ptr, struct node *head, struct node *tail) { struct node *this; this = head->rlink; while(this != head) { if(ptr->key < this->key) { /* 插入位置為中間 */ ptr->rlink = this; ptr->llink = this->llink; this->llink->rlink = ptr; this->llink = ptr; break; } this = this->rlink; } /* 插入位置為尾端 */ if(head->rlink == head || this == head) { ptr->rlink = head; ptr->llink = head->llink; head->llink->rlink = ptr; head->llink = ptr; tail = ptr;} }
雙向鏈結串列 (續) 雙向鏈結串列的刪除動作 刪除串列前端的節點 刪除串列尾端的節點 刪除串列某一特定節點
刪除串列前端的節點 Step 1: ptr = head->rlink; Step 2: head->rlink = ptr->rlink; Step 3: p->rlink->llink = p->llink; Step 4: free(ptr);
刪除串列尾端的節點 Step 1: tail->llink->rlink = head; Step 2: head->llink = tail->llink; Step 3: ptr = tail; Step 4: tail = tail->llink; Step 5: free(ptr);
刪除串列某一特定節點 Assumption: 假設欲刪除this所指的節點 Step 1: this->llink->rlink = this->rlink; Step 2: this->rlink->llink = this->llink; Step 3: free(this);
void delete_node(int del_key, struct node *head, struct node *tail) { struct node *clear, *this; this = head->rlink; while(this != head) { /* 刪除資料於中間 */ if(del_key == this->key) { clear = this; this->llink->rlink = this->rlink; this->rlink->llink = this->llink; if(this == tail) tail = this->llink; break; } this = this->rlink; } free(clear);
鏈結串列的應用 以鏈結串列表示堆疊 以鏈結串列表示佇列 以鏈結串列表示多項式相加
鏈結串列的應用 (續) 以鏈結串列表示堆疊 將堆疊內的資料視為單向鏈結串列中的資料項 Push: 視為將節點加入串列的前端 Pop: 視為刪除前端的節點
鏈結串列的應用 (續) 以鏈結串列表示堆疊 ─ Push void push_stack(int data, struct node *ptr, struct node *top) {ptr = (struct node *) malloc(sizeof(struct node)); ptr->item = data; ptr->next = top; top = ptr; }
鏈結串列的應用 (續) 以鏈結串列表示堆疊 ─ Pop void pop_stack(int data, struct node *top) {struct node *clear if(top ==NULL) printf(“stack-empty”); clear = top; data = top->item; top = top->next; free(clear); }
鏈結串列的應用 (續) 以鏈結串列表示佇列 將佇列內的資料視為單向鏈結串列中的資料項 Enqueue:視為將節點加入串列的尾端 Dequeue:視為刪除前端的節點 由此處開始刪除 由此處開始加入
鏈結串列的應用 (續) 以鏈結串列表示佇列 ─ Enqueue void enqueue(int data, struct node *front, struct node *rear) { ptr = (struct node *) malloc(sizeof(struct node)); ptr->item = data; ptr->next = NULL; if(rear == NULL) front = rear = ptr; else rear->next = ptr; rear = ptr;}
鏈結串列的應用 (續) 以鏈結串列表示佇列 ─ Dequeue void dequeue(int data, struct node *front) {struct node *clear if(front ==NULL) printf(“stack-empty”); data = front->item; clear = front; front = front->next; free(clear); }
鏈結串列的應用 (續) 以鏈結串列表示多項式相加 多項式相加可以用鏈結串列完成,其資料結構為 原理(若有A、B兩多項式) Coef表示變數的係數 Exp表示變數的指數 Link為指下一節點的指標 原理(若有A、B兩多項式) Exp(A) = Exp(B) Exp(A) > Exp(B) Exp(A) < Exp(B)
void poly_add(void) { struct poly *this_n1, *this_n2, *prev; this_n1 = eq_h1; this_n2 = eq_h2; prev = NULL; while(this_n1 != NULL || this_n2 != NULL) /* 當兩個多項式皆相加完畢則結束 */ ptr = (struct poly *) malloc(sizeof(struct poly)); ptr->next = NULL; /* 第一個多項式指數大於第二個多項式 */ if(this_n1 != NULL && (this_n2 == NULL || this_n1->exp > this_n2->exp)) ptr->coef = this_n1->coef; ptr->exp = this_n1->exp; this_n1 = this_n1->next; }
else /* 第一個多項式指數小於第二個多項式 */ if(this_n1 == NULL || this_n1->exp < this_n2->exp) { ptr->coef = this_n2->coef; ptr->exp = this_n2->exp; this_n2 = this_n2->next; } else /* 兩個多項式指數相等,進行相加 */ ptr->coef = this_n1->coef + this_n2->coef; ptr->exp = this_n1->exp; if(this_n1 != NULL) this_n1 = this_n1->next; if(this_n2 != NULL) this_n2 = this_n2->next;
if(ptr->coef != 0) /* 當相加結果不等於0,則放入答案多項式中 */ { if(ans_h == NULL) ans_h = ptr; else prev->next = ptr; prev = ptr; } else free(ptr);