[演算法淺析] 如何在O(1)的時間裡刪除單鏈表的結點,單鏈結點
題目是這樣的:給你一個單鏈表的表頭,再給你其中某個結點的指標,要你刪除這個結點,條件是你的程式必須在O(1)的時間內完成刪除。
由於有的同學對鏈表還不是很熟悉,本文盡量描述的通俗易懂,老鳥請直接跳過前面一大段。
鏈表結構如下:
struct node{ int val; node* next;};
題目不是很難,很快就能想到好辦法:)
首先回顧一下普通的刪除方法,首先通過表頭,找到待刪除結點(設為B)的前一個結點(設為A),將A的指向改一下就行,然後刪除掉B結點就行了。要刪除的結點一定要delete掉,這不僅是個好習慣,而且能避免將來項目中可能造成的記憶體泄露的嚴重問題。
void DeleteNode_On(node *LinkList, node *p){ printf("delete:%d\n", p->val); node *s = LinkList; while(s->next != p) s = s->next; s->next = s->next->next; delete(p);}
這個演算法主要耗時在於尋找前一個結點,所以是O(n)的演算法。
那麼既然要求是O(1),顯然不能再去for一遍了,聯想到數組的刪除,這個問題就比較好解決了。
首先我們很容易就能得到待刪除結點,即B結點的後一個結點C,然後將C的值賦值給B結點的值,相當於數組刪除時候的覆蓋,現在B結點和C結點一模一樣了,接下來就相當簡單了吧,我們不刪B,直接利用B刪掉C就行了,方法簡單,時間O(1)。
但是仔細想想這個演算法有個很明顯的缺陷,如果待刪除結點是最後一個結點呢?這個時候似乎沒有什麼好的解決辦法,只能老老實實的O(n)了。現在我們來看看平均時間複雜度:
符合題目要求。
void DeleteNode_O1(node *LinkList, node *p){ printf("delete:%d\n", p->val); if(p->next != NULL) //如果p不是末尾結點, 則讓後一個結點覆蓋掉p, 然後刪除後一個結點 { p->val = p->next->val; node *tmp = p->next; p->next = p->next->next; delete(tmp); } else // 如果p是末尾結點, 則找到p的前一個結點然後正常刪除 { node *s = LinkList; while(s->next != p) s = s->next; s->next = s->next->next; delete(p); }}
最後附上完整測試代碼:
#include<iostream>using namespace std;struct node{ int val; node* next;};void CreateLinkList(node *LinkList){ node *s = LinkList; for(int i = 0; i < 10; i++) { node *t = new node; t->val = i; t->next = NULL; s = s->next = t; }}void ShowLinkList(node * LinkList){ node *s = LinkList->next; while(s = s->next) printf("%d ", s->val); putchar(10);}void DeleteNode_On(node *LinkList, node *p){ printf("delete:%d\n", p->val); node *s = LinkList; while(s->next != p) s = s->next; s->next = s->next->next; delete(p);}void DeleteNode_O1(node *LinkList, node *p){ printf("delete:%d\n", p->val); if(p->next != NULL) //如果p不是末尾結點, 則讓後一個結點覆蓋掉p, 然後刪除後一個結點 { p->val = p->next->val; node *tmp = p->next; p->next = p->next->next; delete(tmp); } else // 如果p是末尾結點, 則找到p的前一個結點然後正常刪除 { node *s = LinkList; while(s->next != p) s = s->next; s->next = s->next->next; delete(p); }}int main(){ node *LinkList = new node; CreateLinkList(LinkList); ShowLinkList(LinkList); node *p = LinkList->next; for(int i = 0; i < 3; i++) p = p->next; DeleteNode_On(LinkList, p); ShowLinkList(LinkList); p = LinkList->next; for(int i = 0; i < 8; i++) p = p->next; DeleteNode_O1(LinkList, p); ShowLinkList(LinkList); p = LinkList->next; for(int i = 0; i < 4; i++) p = p->next; DeleteNode_O1(LinkList, p); ShowLinkList(LinkList); getchar(); return 0;}
設計一個在帶頭結點的單鏈表中刪除第i個結點的演算法
//刪除節點 刪除第i個節點
int Delete_Positon_LL(LinkList *phead,int i)
{
LinkList p,q;//p為值是x的節點,q是p的前一個節點
int j;
if((*phead)->next == NULL)//如果鏈表為空白,做下溢處理
{
printf("單鏈表為空白!\n");
return 0;
}
if(i == 1)//如果是表頭,表頭後移
{
p=(*phead)->next;
(*phead)->next=(*phead)->next->next;
free(p);//釋放表頭
}
else//從第二個節點尋找值是x的
{
q=(*phead)->next;
p=(*phead)->next->next;
j=2;
//注意先p !=NULL,否則將出現非法訪問操作
while(p !=NULL && j<i )
{
q=p;
p=p->next;
j++;
}
if(p!=NULL)//找到了
{
q->next=p->next;//讓前一個節點指向p的後繼節點
free(p);//刪除節點p
}
else
{
printf("刪除第%d個節點超出範圍.\n",i);
return 0;
}
}
return 1;
}
順序表、單鏈表的刪除演算法
單鏈表的刪除操作是指刪除第i個結點,返回被刪除結點的值。刪除操作也需要從頭引用開始遍曆單鏈表,直到找到第i個位置的結點。如果i為1,則要刪除第一個結點,則需要把該結點的直接後繼結點的地址賦給頭引用。對於其它結點,由於要刪除結點,所以在遍曆過程中需要儲存被遍曆到的結點的直接前驅,找到第i個結點後,把該結點的直接後繼作為該結點的直接前驅的直接後繼。刪除操作
單鏈表的刪除操作
刪除操作的演算法實現如下:
public T Delete(int i)
{
if (IsEmpty()|| i < 0)
{
Console.WriteLine("Link is empty or Position is error!");
return default(T);
}
Node q = new Node();
if (i == 1)
{
q = head;
head = head.Next;
return q.Data;
}
Node p = head;
int j = 1;
while (p.Next != null&& j < i)
{
++j;
q = p;
p = p.Next;
}
if (j == i)
{
q.Next = p.Next;
return p.Data;
}
else
{
Console.WriteLine("The ith node is not exist!");
return default(T);
}
}
演算法的時間複雜度分析:單鏈表上的刪除操作與插入操作一樣,時間主要消耗在結點的遍曆上。如果表為空白則不進行遍曆。當表非空時,刪除第i個位置的結點, i等於1遍曆的結點數最少(1個),i等於n遍曆的結點數最多(n個,n為單鏈表的長度),平均遍曆的結點數為n/2。所以,刪除操作的時間複雜度為O(n)。...餘下全文>>