2021-2-16 連結串列的基本操作與例題
連結串列和陣列作為演算法中的兩個基本資料結構,在程式設計過程中經常用到。儘管兩種結構都可以用來儲存一系列的資料,但又各有各的特點。
陣列的優勢,在於可以方便的遍歷查詢需要的資料。在查詢陣列指定位置(如查詢陣列中的第4個數據)的操作中,只需要進行1次操作即可,時間複雜度為O(1)。但是,這種時間上的便利性,是因為陣列在記憶體中佔用了連續的空間,在進行類似的查詢或者遍歷時,本質是指標在記憶體中的定向偏移。然而,當需要對陣列成員進行新增和刪除的操作時,陣列內完成這類操作的時間複雜度則變成了O(n)。
連結串列的特性,使其在某些操作上比陣列更加高效。例如當進行插入和刪除操作時,連結串列操作的時間複雜度僅為O(1)。另外,因為連結串列在記憶體中不是連續儲存的,所以可以充分利用記憶體中的碎片空間。
說到連結串列,可能有些人還對其概念不是很瞭解。我們可以將一條連結串列想象成環環相扣的結點,就如平常所見到的鎖鏈一樣。連結串列內包含很多結點(當然也可以包含零個結點)。
接下來,我們將對連結串列的基本操作進行一個總結。
1.逆序建單鏈表(頭插)
傳送門:[SDUT 2117](https://acm.sdut.edu.cn/onlinejudge3/problems/2117)
#include<bits/stdc++.h>
using namespace std;
struct node{//結點
int data;
struct node *next;
};
int main()
{
struct node *head,*p;
head=head=(struct node*)malloc(sizeof(struct node));//C語言分配記憶體寫法
head->next=NULL;//頭結點指向NULL
int n;
cin>>n;
for(int i=0;i<n;i++)//建立連結串列
{
p=new node();//C++分配記憶體寫法
cin>>p->data;
p->next=head->next;//p指向的下一個變成了head的下一個
head->next=p;//head的下一個變成了p
}
for (p=head->next;p;p=p->next)//連結串列的輸出
{
if(!p->next) cout<<p->data<<endl;
else cout<<p->data<<" ";
}
return 0;
}
2.順序建單鏈表(尾插)
傳送門:SDUT 2116
#include<bits/stdc++.h>
using namespace std;
struct node{
int data;
struct node *next;
};
int main()
{
struct node *head,*p,*tail;
head=new node();
head->next=NULL;
tail=head;
int n;
cin>>n;
for(int i=0;i<n;i++)
{
p=new node();
cin>>p->data;
p->next=NULL;//p指向NULL
tail->next=p;//tail->next指向p
tail=p;//遊動指標tail移動到p
}
for(p=head->next;p;p=p->next)
{
if(!p->next) cout<<p->data<<endl;
else cout<<p->data<<" ";
}
return 0;
}
3.單鏈表結點的插入
在連結串列中插入一個數據。插入資料也就是插入一個節點.現在我們來考慮如何插入,首先需要知道插入的位置,這個由外界資料給出。當我們知道把資料插入到連結串列中的哪個位置之後,就開始著手如何插入。
傳送門:SDUT 2874
#include<bits/stdc++.h>
using namespace std;
struct node{
int data;
struct node *next;
};
int main()
{
struct node *head,*p,*tail;
int n,i,m,x,j;
while(cin>>n)
{
int num=0;
head=new node();
head->next=NULL;
tail=head;
for(i=0;i<n;i++)
{
struct node *t=head;//建立臨時指標
p=new node();
cin>>m;
if(m>num) //判斷m是否大於連結串列的結點總數
while(t->next)//大於的話迴圈到t->next=NULL,即最後一個節點
t=t->next;
else
for(j=1;j<=m;j++)//否則迴圈到第m個結點
t=t->next;
cin>>x;
p->data=x;
p->next=t->next;//插入令p->next指向t->next
t->next=p;//令t->next指向p
num++;//節點數+1
}
for(p=head->next;p;p=p->next)
{
if(!p->next) cout<<p->data<<endl;
else cout<<p->data<<" ";
}
}
return 0;
}
4.單鏈表結點的刪除
結點的刪除就是把需要刪除的元素直接斷開掉然後把前面的元素和後面的元素連線在一起就可以了,刪除的步驟也是和插入差不多的操作的,也是首先需要定位在需要刪除的位置的前一個位置上,因為刪除的操作和插入可以說是一樣的,都是定位在需要刪除的位置的前一個位置上,然後進行刪除定位後的元素。
傳送門:SDUT 2122
#include<bits/stdc++.h>
using namespace std;
struct node{
int data;
struct node *next;
};
int main()
{
struct node *head,*tail,*p,*q;
head=new node();
head->next=NULL;
tail=head;
int n,i;
cin>>n;
for(i=0;i<n;i++) //頭插建表
{
p=new node();
cin>>p->data;
p->next=head->next;
head->next=p;
}
cout<<n<<endl;//輸出n
for(p=head->next;p;p=p->next)//輸出原連結串列
{
if(!p->next) cout<<p->data<<endl;
else cout<<p->data<<" ";
}
for(p=head->next;p;p=p->next) //迴圈尋找相等的結點
{
struct node *t=head->next;//建立臨時指標
for(q=p->next;q;q=q->next)
{
if(p->data==q->data)
{
while(t->next!=q)//迴圈到q的前一結點
t=t->next;
t->next=q->next;//令t->next指向q的下一結點
n--;//結點數-1
}
}
}
cout<<n<<endl;//輸出刪除後的結點數
for(p=head->next;p;p=p->next)//輸出連結串列
{
if(!p->next) cout<<p->data<<endl;
else cout<<p->data<<" ";
}
return 0;
}
5.單鏈表的逆置
傳送門:SDUT 2118
#include<bits/stdc++.h>
using namespace std;
struct node{
int data;
struct node *next;
};
int main()
{
struct node *head,*p,*tail;
head=new node();
head->next=NULL;
tail=head;
int n;
while(cin>>n&&n!=-1)//輸入連結串列結點的元素n==-1時跳出
{
p=new node();
p->data=n;
p->next=NULL;
tail->next=p;
tail=p;
}
p=head;
struct node *t,*New=NULL;//建立新頭結點
while(p!=NULL)
{
t=p->next;//建立臨時指標
p->next=New;//p->next 指向New
New=p;//遊指New 等於p的結點
p=t;//p移動到開始時的下一結點
}
p=New;
for(;p->next;p=p->next)
{
if(!p->next) cout<<p->data<<endl;
else cout<<p->data<<" ";
}
return 0;
}
其實還有一種比較懶的方法:直接生成頭插連結串列
這個看著玩玩就好
#include<bits/stdc++.h>
using namespace std;
struct node{
int data;
struct node *next;
};
int main()
{
struct node *head,*p,*tail;
head=new node();
head->next=NULL;
tail=head;
int n;
while(cin>>n&&n!=-1)
{
p=new node();
p->data=n;
p->next=head->next;
head->next=p;
}
for(p=head->next;p;p=p->next)
{
if(!p->next) cout<<p->data<<endl;
else cout<<p->data<<" ";
}
return 0;
}
6.單鏈表的歸併
如題,連結串列的歸併即將兩條連結串列合成一條新連結串列(誤
傳送門:SDUT 2119
#include<bits/stdc++.h>
using namespace std;
struct node {
struct node *next;
int data;
};
struct node *creat(int n)//建表
{
struct node *head,*tail,*p;
head=new node();
head->next=NULL;
tail=head;
for(int i=0;i<n;i++)
{
p=new node();
cin>>p->data;
p->next=NULL;
tail->next=p;
tail=p;
}
return head;//返回連結串列的頭結點
};
int main()
{
int n,m;
struct node *head1,*head2,*head,*tail1,*tail2,*tail,*p1,*p2,*p;
head=new node();
head->next=NULL;
tail=head;
cin>>n>>m;
head1=creat(n);//連結串列1
p1=head1->next;//遊指1
head2=creat(m);//連結串列2
p2=head2->next;//遊指2
while(p1!=NULL&&p2!=NULL)//兩條連結串列的都沒到達NULL的時候進行迴圈比較
{
if((p1->data)>(p2->data))//如果遊指1指向的元素大於遊指2的元素
{
tail->next=p2;//在新連結串列中連線上游指2指向的結點
tail=p2;
p2=p2->next;//遊指2 指向下一結點
}
else
{
tail->next=p1;//否則連線遊指1的結點
tail=p1;
p1=p1->next;
}
}
if(p1) tail->next=p1;//如果跳出while迴圈而p1未指向NULL(這說明p2指向NULL)直接將鏈1剩下的結點連線到新連結串列後面
else tail->next=p2;//else 連線鏈2
for(p=head->next;p!=NULL;p=p->next)//輸出新鏈
{
if(p->next==NULL)
cout<<p->data<<endl;
else
cout<<p->data<<" ";
}
return 0;
}
7.單鏈表的拆分
如題,將一條連結串列拆成好幾條新連結串列(誤
傳送門:SDUT 2120
#include<bits/stdc++.h>
using namespace std;
struct node{
struct node *next;
int data;
};
void put(struct node * head)//輸出連結串列
{
struct node *p;
for(p=head->next;p!=NULL;p=p->next)
{
if(p->next==NULL)
cout<<p->data<<endl;
else cout<<p->data<<" ";
}
}
int main()
{
struct node *head,*tail,*head1,*tail1,*head2,*tail2,*p;
head=new node();
head->next=NULL;
tail=head;//原連結串列
head1=new node();
head1->next=NULL;
tail1=head1;//新鏈1
head2=new node();
head2->next=NULL;
tail2=head2;//新鏈2
int n,i,cnt1=0,cnt2=0;
cin>>n;
for(i=0;i<n;i++) //建造原連結串列
{
p=new node();
cin>>p->data;
p->next=tail->next;
tail->next=p;
tail=p;
}
p=head->next; //遊指p指向head->next
while(p)
{
if((p->data)%2==0)//偶數連到鏈1
{
tail1->next=p;
tail1=p;
cnt1++;
}
else//奇數連到鏈2
{
tail2->next=p;
tail2=p;
cnt2++;
}
p=p->next;
}
tail1->next=NULL;//保證連結串列最後指向NULL
tail2->next=NULL;
cout<<cnt1<<" "<<cnt2<<endl;
put(head1);//輸出連結串列
put(head2);
return 0;
}
8.雙向連結串列
雙向連結串列也叫雙鏈表,是連結串列的一種,它的每個資料結點中都有兩個指標,分別指向直接後繼和直接前驅。所以,從雙向連結串列中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。下圖為雙向連結串列的結構圖。
雙向連結串列特點
1.每次在插入或刪除某個節點時, 需要處理四個節點的引用, 而不是兩個. 實現起來要困難一些。
2.相對於單向連結串列, 必然佔用記憶體空間更大一些。.
3.既可以從頭遍歷, 又可以從尾遍歷。
貼一篇詳解雙向連結串列的blog --------------------->傳送門
傳送門:SDUT 2054
#include<bits/stdc++.h>
using namespace std;
struct node{
struct node *next,*prev;
int data;
};
int main()
{
int n,m,i,x;
struct node *head,*tail,*p,*q;
head=new node();
head->next=NULL;
tail=head;
cin>>n>>m;
for(i=0;i<n;i++)
{
p=new node();
cin>>p->data;
p->next=tail->next;
tail->next=p;
p->prev=tail;
tail=p;
}
for(i=0;i<m;i++)
{
int j=0;
cin>>x;
for(p=head->next;p;p=p->next)
{
j++;
if(j==1&&p->data==x)
cout<<p->next->data<<endl;
else if(j==n&&p->data==x)
cout<<p->prev->data<<endl;
else if(p->data==x)
cout<<p->prev->data<<" "<<p->next->data<<endl;
}
}
return 0;
}
9.約瑟夫問題(迴圈連結串列)
再貼一篇迴圈連結串列的blog ------------------------------>傳送門
傳送門:SDUT 1197
#include<bits/stdc++.h>
using namespace std;
struct node
{
int data;
struct node *next;
};
struct node *creat(int n)//建表
{
int i;
struct node *head,*p,*tail;
p =new node();
p->data=1;
p->next=NULL;
head=p;
tail=p;
for(i=2;i<=n;i++)
{
p=new node();
p->data=i;
tail->next=p;
tail=p;
p->next=NULL;
}
tail->next=head;
return head;
}
int sel(struct node *head,int m, int n)//迴圈
{
int cnt = 0,i = 0;
struct node *p,*q;
q = head;
while(q->next!=head)
{
q = q->next;
}
while(cnt<n-1)
{
p=q->next ;
i++;
if(i%m==0)
{
q->next=p->next;
free(p);
cnt++;
}
else
q=p;
}
return q->data;
}
int main()
{
int n,m;
struct node *head;
cin>>n>>m;
head = creat(n);
cout<<sel(head,m,n)<<endl;
return 0 ;
}