1. 程式人生 > >刪除單鏈表中的重複節點(刪除多餘項)

刪除單鏈表中的重複節點(刪除多餘項)

題目:如何刪除單鏈表中的重複節點(即保證每個元素只出現一次,刪除多餘的,且後來出現的元素)。
一個沒有排序的單鏈表,如 list = {a, 1, x, b, e, f, f, e, a, g, h, b, m},請去掉重複項,並保留原順序,以上鍊表去掉重複項後為 newList = {a, 1, x, b, e, f, g, h, m},請寫出一個高效的演算法。

思路分析:
第一種方法:因為要求演算法高效,且考慮到時間比空間更重要,因此使用hash法。
步驟:
(1)建立一個hash表,key為連結串列中已經遍歷的節點內容,初始時為空。
(2)從頭開始遍歷單鏈表中的節點。
<1> 如果節點內容已經在hash表中存在,則刪除此節點,繼續向後遍歷。
<2> 如果節點內容不在hash表中,則保留此節點,將節點內容新增到hash表中,繼續向後遍歷。
(3)注意點:
<1> 刪除時需要知道前一節點。
<2> 此處連結串列中儲存的是char型變數,所以雜湊表為含有256個元素的陣列。
<3> 如果儲存的是其他資料型別,則可以使用STL中的hash_set容器。

第二種方法:如果不能使用額外的儲存空間,直接在原始連結串列上進行操作。(非遞迴)
步驟:
(1)建立指標p,用於遍歷連結串列;
(2)建立指標q,q遍歷p後面的結點,並與p數值比較;
(3)建立指標r,r儲存需要刪掉的結點,再把需要刪掉的結點的前後結點相接。由此去掉重複值。

第三種方法:遞迴實現。

具體實現如下:

#include <iostream>

// 單鏈表節點結構如下
typedef struct node
{
    char data;
    struct node *next;
} NODE;


// 尾插法建立單鏈表(帶頭節點)
NODE *createEnd
(char arr[], int len) { NODE *head = (NODE *)malloc(sizeof(NODE)); // 生成頭節點 head->next = NULL; NODE *end = head; // 尾指標初始化 for (int i = 0; i < len; i++) { NODE *p = (NODE *)malloc(sizeof(NODE)); // 為每個陣列元素建立一個節點 p->data = arr[i]; end->next = p; // 將節點p插入到終端節點之後 end = p; } end->next
= NULL; // 單鏈表建立完畢,將終端節點的指標域置空 return head; } // 單鏈表列印 void print(NODE *head) { if (head == NULL) return; NODE *p = head->next; while (p != NULL) { printf("%c ", p->data); p = p->next; } } // 第三種方法:遞迴方法 NODE *delSame_3(NODE *head) { NODE *p, *temp = head; // 遞迴過程中head是在不斷變化的,但初始時temp都指向新的head。 if (head->next == NULL) return head; head->next = delSame_3(head->next); // 遞迴到head->next指向連結串列的尾節點;此時head指向連結串列倒數第二個節點。 p = head->next; // 讓p指向head的下一個節點,注意此時temp=head; while (p != NULL) { if (head->data == p->data) // 單次遞迴中,head是不變的,每次都把head的資料和head之後所有節點的資料比較,若相同,則刪除該節點。 { temp->next = p->next; free(p); p = temp->next; } else { p = p->next; temp = temp->next; // temp初始時是指向新的head的,之後作為臨時變數,隨著p一起後移,始終作為p的前驅節點,是為了當p節點的資料和head資料一樣時,在刪除p節點時,用temp->next來儲存p的後繼節點。 } } return head; } // 第二種方法:非遞迴實現去重 NODE *delSame_2(NODE *head) { NODE *p,*q,*r; p = head->next; // 適用於有頭節點的單鏈表;對於不帶頭節點的單鏈表,此處改為 p=head 即可。 while(p != NULL) // p用於遍歷連結串列 { q = p; while(q->next != NULL) // q遍歷p後面的結點,並與p數值比較 { if(q->next->data == p->data) { r = q->next; // r儲存需要刪掉的結點 q->next = r->next; // 需要刪掉的結點的前後結點相接 free(r); } else q = q->next; } p = p->next; } return head; } // 第一種方法:hash表去重 NODE *delSame(NODE *head) { if (head == NULL || head->next == NULL) return head; const int size = 256; unsigned int count[size]; // 可用 unsigned int count[size] = {0}; 將陣列中每一個元素都初始化為0for (int i = 0; i < size; i++) // 可能上述方法,不一定對所有編譯器都適用,所以採用迴圈的方式更穩妥。 count[i] = 0; // count[(unsigned char)(head->data)] = 1; // 當使用沒有頭節點的單鏈表時,開啟此行程式碼即可。 NODE *p = head; // head是單鏈表的頭節點,注意,頭節點沒有資料,它的存在僅僅是為了方便計算。 NODE *q = p->next; NODE *r; while (q != NULL) { if (count[(unsigned char)(q->data)] == 0) { count[(unsigned char)(q->data)] = 1; p = q; q = q->next; // 始終讓p作為q的前驅節點 } else { r = q; // r儲存要刪除的節點 p->next = q->next; q = p->next; // 始終讓p作為q的前驅節點 free(r); } } return head; } int main(int argc, const char * argv[]) { char arr[] = "a1xbeffeaghbm"; int len = (int)strlen(arr); NODE *head = createEnd(arr, len); // 去重之前 print(head); printf("\n"); delSame(head); // 去重之後 print(head); printf("\n"); return 0; }