链表的简单入门
寒假刚刚把链表的题目刷完,感觉对链表有了一点认识,分享给大家,希望大家看了我的博文后都能有所进步,有所收获,另外有误的地方欢迎大家来指正。
链表是一种常见的数据结构。当使用数组存放数据时,要先指定数组中包含元素的个数,即数组的长度。当数据超过了数组的大小时,便不能够将内容完全保存。如果数据远小于数组的长度,用数组申请空间的方式就非常浪费空间。
所以就希望有一种储存方式,其元素的个数不受限定当添加新的元素时,储存的个数也会随之改变,这种存储方式就是链表。
下图为结构体的示意图
-
链表的建立
要建立一个链表,首先要有一个头指针,头指针中一般只存放着一个地址(当然头指针中也可以存放数据,在接触循环链表时会用到),该地址为一个变量的地址,也就是说头指针指向一个变量,这个变量称为元素。链表中每一个元素包括数据部分和指针部分。数据部分用来存放元素所包含的数据,而指针部分用来指向下一个元素。最后一个元素的指针指向NULL,表示指向的地址为空。 -
有关函数
如malloc函数
void *malloc(unsigned int size)
该函数的功能是动态的分配一块size大小的内存空间,错误时返回NULL。
void *calloc(unsigned int size)
该函数的功能是动态的分配n个长度为size的内存空间,错误时返回NULL。
void free(void *ptr)
该函数的功能是能使用由指针ptr指向的内存区,使部分内存区能够被其他变量使用,ptr是最近一次调用calloc或malloc函数返回的值。free函数没有返回值。
下面由一个题来讲解函数的使用方法,以及链表的建立,和链表的输出顺序建立链表
#include <iostream>
#include <malloc.h>
using namespace std;
struct node
{
int data;
node *next;
};
struct node * create(int n){
node *head=(struct node*)malloc(sizeof(struct node)) ; //前面一个括号是强制类型转换,后面一个括号是要分配的空间大小
//我个人比较喜欢c++的申请内存空间建立节点的方式 node *head= new node;
node *tail=(struct node*)malloc(sizeof(struct node));
head->next=NULL; //让新建的头指针指向空
tail=head; //建立一个辅助指针便于访问新建立元素前一个元素的位置
for(int i=0;i<n;i++){
node* p=(struct node*)malloc(sizeof(struct node));
scanf("%d",&p->data);
p->next=NULL; //新建立节点指向空(顺序建立链表)
tail->next=p; //让新建立的元素与前一个元素相连
tail=p; //辅助指针的移动
}
return head;
}
void show(node *head)
{
node *p=head->next;
while(p!=NULL){
if(p->next==NULL) printf("%d\n",p->data); //此处注意空格 搞不好会Pe
else printf("%d ",p->data);
p=p->next;
}
}
int main()
{
int n;
cin>>n;
node * head=create(n);
show(head);
return 0;
}
逆序建立链表与顺序建立链表相似,逆序建立链表的新元素在head的后边插入,顺序建立链表新元素放在旧元素之后。 再给出一段代码注意区别。
struct node *create(int n)
{
node *head=new node;
node tail;
head->next=NULL;
tail=head;
for(int i=0;i<n;i++){
node* p=new node;
scanf("%d",&p->data);
p->next=tail->next; //注意此处tail=head不移动
tail->next=p;
}
return head;
}
链表的建立和输出基本的东西就这些。
-
链表中元素的删除
链表可以对其中的一个元素进行删除操作,具体的步骤如下:
1 让要删除元素前一个元素指向要删除元素的后一个元素
2 把要删除的元素释放(free)掉
注意:因为要借助被删除元素的前一个元素,所以需要一个辅助指针
下面根据一个题目来进一步认识链表元素的删除链表指定元素的删除
#include <iostream>
#include <malloc.h>
using namespace std;
struct node {
int data ;
node *next;
};
node *create(int n){
node *head=new node;
node *tail;
head->next=NULL;
tail=head;
for(int i=0;i<n;i++){
node *p=new node;
scanf("%d",&p->data);
p->next=NULL;
tail->next=p;
tail=p;
}
return head;
}
void show(node *head)
{
node *p=head->next;
while(p!=NULL){
if(p->next==NULL) printf("%d\n",p->data);
else printf("%d ",p->data);
p=p->next;
}
}
node *cut(int n,int m,node *head){
node *pre,*p,*q;
pre=head;
q=head->next;
while(q!=NULL)
{
if(q->data==m)
{
pre->next=q->next; //此处注意辅助指针的位置向后移动了一位,所以下一步只需要移动另一个指针
free(q); //释放空间显得严谨,同时防止未知错误的发生
q=q->next;
n--;
}
else{
pre=pre->next;
q=q->next;
}
}
cout<<n<<endl;
return head;
}
int main()
{
int n;
cin>>n;
node *head;
head=create(n);
cout<<n<<endl;
show(head);
int m;
cin>>m;
head=cut(n,m,head);
show(head);
return 0;
}
-
链表元素的插入
理解了链表以后,链表元素的插入就变的轻松起来
链表元素的插入步骤如下:
1 断开原来的联系
2 前一个元素指向新元素
3 新元素指向后一个元素
下面给出一道题目来进一步认识链表元素的插入:师--链表的节点插入
#include <iostream>
using namespace std;
struct node {
int data;
node *pnext;
};
void Insert(node* head, int m,int data1)
{
node *p=head;
while(m--&&p->pnext!=NULL)
{
p=p->pnext;
}
node *ptr=new node;
ptr->data=data1;
ptr->pnext=p->pnext; //体现了节点的插入
p->pnext=ptr;
}
void show(node *head)
{
node *pe=head->pnext;
while(pe!=NULL)
{
printf("%d ",pe->data);
pe=pe->pnext;
}
}
int main()
{
int m,x;
int n;
while(cin>>n){
node *head=new node;
head->pnext=NULL;
for(int i=1;i<=n;i++){
scanf("%d%d",&m,&x);
Insert(head,m,x);
}show(head);
}
return 0;
}
-
双向链表
双向链表与链表相似,只不过双向链表有两个指着front和next分别指向前一个元素和后一个元素,但要注意边界时front或next指向空的情况,使用双向链表让链表的删除操作变得更简单,不用辅助指针就可以实现。
给出一个题目来了解双向链表:双向链表
#include <iostream>
using namespace std;
struct node{
int data;
node* front,*back;
};
node *creat(int n)
{
node* head=new node;
node * tail;
head->front=NULL; //注意此处的初始化
head->back=NULL;
tail=head;
for(int i=0;i<n;i++){
node * p=new node;
scanf("%d",&p->data);
p->back=NULL;
tail->back=p;
p->front=tail;
tail=p;
}
return head;
}
void search(int x,node *head){
node *p=head->back;
while(p->data!=x)
p=p->back;
if(p->front==head)
printf("%d\n",p->back->data);
else if(p->back==NULL)
printf("%d\n",p->front->data);
else
printf("%d %d\n",p->front->data,p->back->data);
}
int main(){
int n,m;
int x;
cin>>n>>m;
node* head=creat(n);
for(int i=0;i<m;i++)
{
cin>>x;
search(x,head);
}
return 0;
}
-
循环链表
循环链表和单向链表相似,不同点是循环链表的最后一个元素指向head,还有一个注意点是循环链表的头指针到底要不要储存元素,两者都可,只不过不储存数据的时候需要另外判断好几个地方,所以头指针储存数据就显得方便,我个人喜欢用*我懒* 。
下面两个题目可以更好的理解循环链表约瑟夫问题
#include <iostream>
#include <malloc.h>
using namespace std;
struct node {
int data;
node *next;
};
node * create(int n)
{
node *head=new node ;
node *tail;
head->data=1;
head->next=NULL;
tail=head;
for(int i=2;i<=n;i++)
{
node *p=new node;
p->data=i;
p->next=NULL;
tail->next=p;
tail=p;
}
tail->next=head;
return head;
}
int main()
{
int n,m;
cin>>n>>m;
node *head=create(n);
node *q,*p;
q=head;
while(q->next!=head)
q=q->next;
int con=0;
while(q->next!=q)
{
p=q->next;
con++;
if(con==m)
{
q->next=p->next;
free(p);
con=0;
}
else q=p;
}
printf("%d",q->data);
return 0;
}
不敢死队问题
#include <iostream>
#include <malloc.h>
using namespace std;
struct node{
int data;
node *next;
};
node * create(int n)
{
node *head=new node;
node *tail;
head->next=NULL;
head->data=1;
tail=head;
for(int i=2;i<=n;i++)
{
node *p=new node;
p->data=i;
p->next=NULL;
tail->next=p;
tail=p;
}
tail->next=head;
return head;
}
int main()
{
int n;
while(~scanf("%d",&n)&&n){
node *head;
node *p,*q;
head=create(n);
q=head;
while(q->next!=head)
q=q->next;
int con=0,ans=0;
while(q->next!=q)
{
p=q->next;
con++;
if(con==5){
if(p->data==1)
break;
q->next=p->next;
free(p);
con=0;
ans++;
}
else q=p;
}
printf("%d\n",ans+1);
}
return 0;
}
- 最后来说一下数组与链表的优缺点:
1 数组是顺序排列的,访问第一个地址的索引和访问最后一个地址的索引,消耗的时间基本所差无几,很快;而链表地址却不是相连的,所以效率不如数组。
2 数组的插入和删除很麻烦,所以引入了链表;
- 结语
这是本人写的第一篇博文,有很多不足的地方,随着以后发博文的数量增多相信我的博文能够更加的完善,如果能够帮助到大家,我会感到非常的荣幸。
还有剩余的链表的题目,我会再后续发博文。