在链表中,每个数据元素都配有一个指针,这意味着,链表上的每个“元素”都长下图这个样子:
数据域用来存储元素的值,指针域用来存放指针。数据结构中,通常将上图这样的整体称为结点。
在 C 语言中,可以用结构体表示链表中的结点,例如:
struct node{
char data; //代表数据域
struct node * next; //代表指针域,指向直接后继元素
};
我们习惯将结点中的指针命名为 next,因此指针域又常称为“Next 域”。
头结点、头指针和首元结点
一个完整的链表应该由以下几部分构成:
头指针:一个和结点类型相同的指针,它的特点是:永远指向链表中的第一个结点。上文提到过,我们需要记录链表中第一个元素的存储位置,就是用头指针实现。
结点:链表中的节点又细分为头结点、首元结点和其它结点:
头结点:某些场景中,为了方便解决问题,会故意在链表的开头放置一个空结点,这样的结点就称为头结点。也就是说,头结点是位于链表开头、数据域为空(不利用)的结点。
首元结点:指的是链表开头第一个存有数据的结点。
其他节点:链表中其他的节点。
也就是说,一个完整的链表是由头指针和诸多个结点构成的。每个链表都必须有头指针,但头结点不是必须的。
例如,创建一个包含头结点的链表存储 {1,2,3},如下图所示:
再次强调,头指针永远指向链表中的第一个结点。换句话说,如果链表中包含头结点,那么头指针指向的是头结点,反之头指针指向首元结点。
1.单链表
(1)链表的创建,实现步骤如下:
定义一个头指针;
创建一个头结点或者首元结点,让头指针指向它;
每创建一个结点,都令其直接前驱结点的指针指向它。
#include <stdio.h>
struct node {
char name[16];
struct node *next;
};
struct node head;//这里是创建一个头结点
struct node *pthead=&head;
struct node *pttail=&head;//创建头尾指针,你们可以不用,个人习惯
void list_travel(struct node *pthead)
{
struct node *p = pthead->next;
while(p){
printf("%s ",p->name);
p=p->next;//指向下一个节点
}
printf("\n");
}
int main()
{
struct node *p;
pthead->next=NULL;//创建头结点
printf("please input name:");
while(1)
{
p=malloc(sizeof(*p));
scanf("%s",p->name);
p->next=NULL;
if(strcmp(p->name,"#")==0)
{
free(p);
break;
}
// 在尾部添加节点
pttail->next = p;
pttail = pttail->next; //p;
}
//链表的遍历
printf("链表为:: ");
list_travel(pthead);
return 0;
}
(2)链表的插入
如下图,我们要在p的后面插入m,只需将m指向zheng,在将p指向m即可。
#include <stdio.h>
struct node {
char name[16];
struct node *next;
};
struct node head;//这里是创建一个头结点
struct node *pthead=&head;
struct node *pttail=&head;//创建头尾指针,你们可以不用,这里是为了更好的帮助大家解
void list_travel(struct node *pthead)
{
struct node *p = pthead->next;
while(p){
printf("%s ",p->name);
p=p->next;
}
printf("\n");
}
int list_insert_node_after( struct node *pthead,char *findname, char *name )//头结点,要插入的位置,插入的数据
{
struct node *p = pthead->next;
while( p ){
if( strcmp(p->name,findname) ==0 ){
// p结点与要插入的位置相等
struct node *m=malloc(sizeof(*m));
strcpy(m->name,name); m->next=NULL;
//在p后面插入m
m->next=p->next; p->next=m;
return 0;
}
p=p->next;
}
/*here p==NULL*/
return -1;
}
int main()
{
struct node *p;
pthead->next=NULL;//创建头结点
printf("please input name:");
while(1)
{
p=malloc(sizeof(*p));
scanf("%s",p->name);
p->next=NULL;
if(strcmp(p->name,"#")==0)
{
free(p);
break;
}
// 在尾部添加节点
pttail->next = p;
pttail = pttail->next; //p;
}
//travel
printf("链表为:: ");
list_travel(pthead);
//在节点后插入
list_insert_node_after(pthead,"wu","zhang");
printf("插入后: ");
list_travel(pthead);
return 0;
}
(3)链表的删除
删除就是指针直接跳过档期啊节点,指向下一个节点就可以完成删除,具体如下图:删除节点zheng
实现代码:
#include <stdio.h>
struct node {
char name[16];
struct node *next;
};
struct node head;//这里是创建一个头结点
struct node *pthead=&head;
struct node *pttail=&head;//创建头尾指针,你们可以不用,这里是为了更好的帮助大家解
void list_travel(struct node *pthead)
{
struct node *p = pthead->next;
while(p){
printf("%s ",p->name);
p=p->next;
}
printf("\n");
}
int list_insert_node_after( struct node *pthead,char *findname, char *name )//头结点,要插入的位置,插入的数据
{
struct node *p = pthead->next;
while( p ){
if( strcmp(p->name,findname) ==0 ){
// p结点与要插入的位置相等
struct node *m=malloc(sizeof(*m));
strcpy(m->name,name); m->next=NULL;
//在p后面插入m
m->next=p->next; p->next=m;
return 0;
}
p=p->next;
}
/*here p==NULL*/
return -1;
}
int list_delete_node(struct node *pthead, char *name)
{
struct node *pre = pthead;
struct node *p = pthead->next;
while(p){
if(strcmp(p->name,name)==0){
//找到要删除的结点
pre->next=p->next; free(p);
return 0;
}
p=p->next;
pre = pre->next;
}
return -1;
}
int main()
{
struct node *p;
pthead->next=NULL;//创建头结点
printf("please input name:");
while(1)
{
p=malloc(sizeof(*p));
scanf("%s",p->name);
p->next=NULL;
if(strcmp(p->name,"#")==0)
{
free(p);
break;
}
// 在尾部添加节点
pttail->next = p;
pttail = pttail->next; //p;
}
//travel
printf("链表为:: ");
list_travel(pthead);
//在节点后插入
list_insert_node_after(pthead,"wu","zhang");
printf("插入后: ");
list_travel(pthead);
//删除
list_delete_node(pthead,"zheng");
printf("删除后 : ");
list_travel(pthead);
return 0;
}
2.双向链表
双向链表与单向链表比较就是多了一个直接前驱,可以进行双向操作,可进可退。下面给大家举个例子:和上面单链表一致,创建zhou wu zheng wang双向链表实现创建、插入和删除。
(1)创建。上一个节点的后继指向下一个节点的前驱,形成一个环。
//双链表的创建
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct dnode {
char name[16];
struct dnode *next,*pre;
};
struct dnode head;
struct dnode *pthead=&head,*pttail=&head;
//头尾指针,双向操作
void dlist_travel(struct dnode *pthead)
{
struct dnode *p = pthead->next;
printf("从头遍历:");
while(p){
printf("%s ",p->name);
p=p->next;
}
printf("\n");
printf("从尾部遍历:");
p=pttail;
while(p!=pthead){
printf("%s ",p->name);
p=p->pre;
}
printf("\n");
}
void main()
{
//创建头节点
printf("请输入name,以#号结束:");
while(1){
struct dnode *p = malloc(sizeof(*p));
p->pre = p->next = NULL;
scanf("%s",p->name);
if(strcmp(p->name,"#") ==0){
free(p);
break;
}
//链表的创建,增加节点
p->pre = pttail;
pttail->next = p;
pttail=pttail->next;
}
//遍历
dlist_travel(pthead);
}
(2)插入:我们直接看图,要在n和m的中间插入有一个节点p,n的后继指向p的前驱,p的前驱指向节点n,p的后继又指向节点m,m的前驱又指向节点p,形成一个环,就可以完成节点的插入,相信大家一看图就明白了。
//双链表的插入
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct dnode {
char name[16];
struct dnode *next,*pre;
};
struct dnode head;
struct dnode *pthead=&head,*pttail=&head;
//头尾指针,双向操作
void dlist_travel(struct dnode *pthead)
{
struct dnode *p = pthead->next;
printf("从头遍历:");
while(p){
printf("%s ",p->name);
p=p->next;
}
printf("\n");
printf("从尾部遍历:");
p=pttail;
while(p!=pthead){
printf("%s ",p->name);
p=p->pre;
}
printf("\n");
}
int dlist_insert_before(struct dnode *pthead, char *findname, char *name)
{
struct dnode *m=pthead->next;
while( m ){
if(strcmp(m->name,findname) ==0 ){
struct dnode *n = m->pre;
struct dnode *p = malloc(sizeof(*p));
strcpy(p->name,name);
//插入
n->next = p; p->pre = n;
p->next = m; m->pre = p;
return 0;
}
m=m->next;
}
return -1;
}
void main()
{
//创建头节点
printf("请输入name,以#号结束:");
while(1){
struct dnode *p = malloc(sizeof(*p));
p->pre = p->next = NULL;
scanf("%s",p->name);
if(strcmp(p->name,"#") ==0){
free(p);
break;
}
//链表的创建,增加节点
p->pre = pttail;
pttail->next = p;
pttail=pttail->next;
}
//遍历
dlist_travel(pthead);
//在后面插入
dlist_insert_before(pthead, "zheng", "sun");
printf("插入后:\n");
dlist_travel(pthead);
}
(3)删除:如图,删除节点p,m的next域指向n,n的前驱指向m形成一个环就可以完成双链表的删除操作。
//双链表的删除
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct dnode {
char name[16];
struct dnode *next,*pre;
};
struct dnode head;
struct dnode *pthead=&head,*pttail=&head;
//头尾指针,双向操作
void dlist_travel(struct dnode *pthead)
{
struct dnode *p = pthead->next;
printf("从头遍历:");
while(p){
printf("%s ",p->name);
p=p->next;
}
printf("\n");
printf("从尾部遍历:");
p=pttail;
while(p!=pthead){
printf("%s ",p->name);
p=p->pre;
}
printf("\n");
}
int dlist_insert_before(struct dnode *pthead, char *findname, char *name)
{
struct dnode *m=pthead->next;
while( m ){
if(strcmp(m->name,findname) ==0 ){
struct dnode *n = m->pre;
struct dnode *p = malloc(sizeof(*p));
strcpy(p->name,name);
//插入
n->next = p; p->pre = n;
p->next = m; m->pre = p;
return 0;
}
m=m->next;
}
return -1;
}
int dlist_delete(struct dnode *pthead,char *name)
{
struct dnode *p = pthead->next;
while(p){
if(strcmp(p->name,name)==0){
//删除
struct dnode *m=p->pre;
struct dnode *n=p->next;
m->next=n;
n->pre=m;
free(p);
return 0;
}
p=p->next;
}
return -1;
}
void main()
{
//创建头节点
printf("请输入name,以#号结束:");
while(1){
struct dnode *p = malloc(sizeof(*p));
p->pre = p->next = NULL;
scanf("%s",p->name);
if(strcmp(p->name,"#") ==0){
free(p);
break;
}
//链表的创建,增加节点
p->pre = pttail;
pttail->next = p;
pttail=pttail->next;
}
//遍历
dlist_travel(pthead);
//在后面插入
dlist_insert_before(pthead, "zheng", "sun");
printf("插入后:\n");
dlist_travel(pthead);
//删除
dlist_delete(pthead,"zheng");
printf("删除后:\n");
dlist_travel(pthead);
}