刪除單鏈表中的重複節點(刪除多餘項)
阿新 • • 發佈:2019-01-05
題目:如何刪除單鏈表中的重複節點(即保證每個元素只出現一次,刪除多餘的,且後來出現的元素)。
一個沒有排序的單鏈表,如 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}; 將陣列中每一個元素都初始化為0。
for (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;
}