注意:本人也是小白,如果出现错误希望各位读者能够包容
文章目录
- 前言
- 一、单链表的结构定义
- 二、单链表的基本操作
- 1.单链表的初始化
- 2.单链表的创建
- 1.头插法
- 头插法图片讲解
- 2.尾插法
- 尾插法图片讲解
- 3.头插法和尾插法的对比
- 3.单链表的输出
- 4.单链表的插入
- 图片讲解(来自网上,仅供参考)
- 5.单链表的删除
- 图片讲解(来自网上,仅供参考)
- 三、源码
前言
这次分享的是数据结构中单链表的创建和基本操作,这一部分是属于比较基础的内容,越是基础的越要投入精力去学习,不能眼高手低。在编写这篇博客的时候也出现了许多错误,这也算是一篇查漏补缺的博客。希望对大家有帮助。
一、单链表的结构定义
常规定义下的单链表,一般会包含数据域和指针域。
typedef struct node {int data;//数据域struct node* next;//指针域
}node,*linklist;//struct node可以用node替换 struct node*可以用linklist替换
//例如:struct node p p是结构体node的变量 node q q也是结构体node的变量
//struct node *p p是结构体node的指针变量 linklist q q也是结构体node的指针变量
//想要详细了解,可以去查阅typedef和struct的用法
这个地方会出现两个让人比较迷的东西:结构指针node和linklist。本质上(可以自行去了解指针的深层的机制)而言,这两种类型是等价的。通常用linklist说明指针变量,强调它是某个单链表的头指针变量,定义为linklist L,L表示头指针。node用来定义单链表中结点的指针,例如node *p,p为结点的指针变量,p也可以定义为头结点。但是在方法的编写时,这两种定义会混合使用,非常容易迷惑我们的思维。所以我接下来的所有方法都只使用node来定义(当然你也可以两种都使用)。
二、单链表的基本操作
1.单链表的初始化
代码如下(示例):
//初始化链表
void startlist(node* l) {l->next = NULL;
}
/*当然在主函数中是需要创建头结点的,你也可以换一种写法,可以不需要分配头结点内存的写法,请自行查阅node* l;l = (node*)malloc(sizeof(node));//分配头结点内存startlist(l);
*/
2.单链表的创建
1.头插法
//头插法创建单链表
void creatfromhead(node* head) {node* s;//新建一个结点指针变量int i = 1;int j;while (i != 0) {scanf("%d", &j);if (j != EOF) {s = (node*)malloc(sizeof(node));//新建一个结点,node*可以替换成linklists->data = j;//把输入的值赋值给新结点的指针域s->next = head->next;//把新结点插入到表头head->next = s;//头结点始终要放在表头}else {i = 0;}}
}
头插法图片讲解
//头插法核心代码
head->next = NULL;
s->next = head->next;
head->next = s;
单个结点
原始状态
第一个元素的插入过程(注意:1和2的过程不能颠倒,否则会导致链表缺失)
1表示把新结点插入到表头
2表示头结点始终要放在表头
第一个元素插入后
第二个元素的插入过程(其他元素也类似)
第二个元素插入后
2.尾插法
//尾插法
void creatfromtail(node* head) {node* s;//新建一个结点指针变量node* tail;//在建立一个尾结点指针变量int i = 1;int j;tail = head;//很重要的一步,令尾指针tail指向头结点head,便于做尾插入while (i != 0) {scanf("%d", &j);if (j != -1) {s = (node*)malloc(sizeof(node));//建立一个新结点s->data = j;tail->next = s;//尾指针的指针域指向新结点head(换一句话说,就是将新结点放在最后面)tail = s;//再让尾指针放在s后面,尾指针永远在最后面}else {i = 0;tail->next = NULL;//尾指针的指针域设置为空,表示链表结束}}
}
尾插法图片讲解
//核心代码
tail = head;
s->next = NULL;
tail->next = s;
tail = s;
原始状态
第一个元素的插入过程(注意:2和3的顺序不能颠倒,否则会导致链表生成出错)
1表示尾指针为空,链表结束
2表示将新结点放在链表的最后面
3表示尾指针一直指向链表的最后一个结点
第一个元素插入后
第二个元素插入的过程(其余元素的插入也类似)
第二个元素插入后
3.头插法和尾插法的对比
头插法建立链表的算法简短易懂,但是生成链表的结点顺序与原来输入的顺序相反,而用尾插法建立的链表可使输入和生成的结点顺序相同
原因:
根据上面的头插法和尾插法的算法,我们很容易知道,当用头插法依次插入值分别为1,2,3,4,5的结点(也叫做元素)后,单链表会如下图所示:
但是用尾插法同样插入值分别为1,2,3,4,5的结点后,单链表却会如下图所示:
而在这两个链表中,输出链表中各个元素的值只能从已知的头结点head开始遍历,所以分别用头插法和尾插法创建链表后,依次输出的元素的值刚好是相反的
3.单链表的输出
//输出链表
void printlist(node *l) {printf("该链表的内容为\n");;while (l->next != NULL) {printf("%d\t", l->next->data);l = l->next;}printf("\n");
}
4.单链表的插入
//单链表的插入
void enterlist(node* l, int i, int e) {node* p, * s;int k = 0;if (i <= 0) {printf("插入的位置不合理\n");}p = l;while (p != NULL && k < i - 1) {//查找第i-1个结点p = p->next;k++;}if (p == NULL) {//当遍历完整个表也没找到时,说明插入位置不合理(i过大) printf("插入的位置不合理\n");}s = (node*)malloc(sizeof(node));s->data = e;s->next = p->next;// 因为p时第i-1个结点 故 s的指针域应该指向第i个结点 p->next = s;printf("插入成功\n");
}
图片讲解(来自网上,仅供参考)
在结点p之前插入一个新的结点q:对于不带头结点的单链表,结点p的位置有所不同,插入操作有以下两种情况:
1)在链表的表头插入:
(1)创建一个新的结点q。(2)将此结点的数据域赋值为e,并将它的next指针指向第一个结点,即L。(3)将L修改为指向新的结点q。 操作示意图如下:
2)在链表的中间插入
(1)创建一个新的结点q。
(2)将此结点的数据域赋值为e,并将它的next指针指向p。
(3)查找到p的前驱结点pre。
(4)将pre的next指针指向新创建的结点q。
操作示意图如下:
//不带头结点的单链表的插入操作void LinkedListInertQE1(LinkedList L, LinkedList p, ElemType e){q=(LNode *)malloc(sizeof(LNode)); //创建一个新的结点qif(q==NULL){printf("申请空间失败!");exit(0);}q->data=e;if(p==L) //在表头插入{q->next=L;L=q;}else //在表的中间进行插入{pre=L;while((pre!=NULL)&&(pre->next!=p)) //寻找p的前驱pre=pre->next;q->next=pre->next;pre->next=q;}}//带头结点的单链表的插入操作void LinkedListInertQE2(LinkedList L, LinkedList p, ElemType e){q=(LNode *)malloc(sizeof(LNode)); //创建一个新的结点qif(q==NULL){printf("申请空间失败!");exit(0);}q->data=e;//插入新的结点pre=L;while((pre!=NULL)&&(pre->next!=p)) //寻找p的前驱pre=pre->next;q->next=pre->next;pre->next=q;}
5.单链表的删除
//单链表的删除操作
void dellist(node* l, int i) {node* p, * r;int k = 0;p = l;while (p->next != NULL && k < i - 1) {p = p->next;k++;}if (p->next == NULL) {printf("删除位置不合法\n");}r = p->next;p->next = r->next;printf("删除的元素是%d\n", r->data);free(r);
}
图片讲解(来自网上,仅供参考)
删除链表中的某个元素e,如果e在链表中出现不止一次,将删除第一次出现的e,否则什么也不做。 用p找到元素e所在的结点:
1)p是链表中的第一个结点 (1)将L指向p->next。 (2)释放p。 示意图如下:
2)p是链表中的其他结点
(1)找到p的前驱结点pre。
(2)将pre->next指向p->next。
(3)释放p。
示意图如下:
//不带头结点的单链表的删除操作void LinkedListDeleteQE1(LinkedList L, LinkedList p, ElemType e){pre=L;while((pre!=NULL)&&(pre->next->data!=e)) //查找元素e的前驱pre=pre->next;p=pre->next;if(p!=NULL) //找到需要删除的结点{if(p==L) //删除的是第一个结点L=p->next;else //删除的是其他结点pre->next=p->next;free(p);}}//带头结点的单链表的删除操作void LinkedListDeleteQE2(LinkedList L, LinkedList p, ElemType e){pre=L;while((pre!=NULL)&&(pre->next->data!=e)) //查找元素e的前驱pre=pre->next;p=pre->next;if(p!=NULL) //找到需要删除的结点{pre->next=p->next;free(p);}}
三、源码
#include<stdio.h>
#include<stdlib.h>
#define _CRT_SECURE_NO_WARNINGS//链表结构
typedef struct node {int data;struct node* next;
}node,*LinkList;//初始化链表
void startlist(node* l) {l->next = NULL;
}//头插法
void creatfromhead(node* head) {node* s;int i = 1;int j;while (i != 0) {scanf("%d", &j);if (j != EOF) {s = (node*)malloc(sizeof(node));s->data = j;s->next = head->next;head->next = s;}else {i = 0;}}
}//尾插法
void creatfromtail(node* head) {node* s;node* tail;int i = 1;int j;tail = head;while (i != 0) {scanf("%d", &j);if (j != -1) {s = (node*)malloc(sizeof(node));s->data = j;tail->next = s;tail = s;}else {i = 0;tail->next = NULL;}}
}//输出链表
void printlist(node *l) {printf("该链表的内容为\n");;while (l->next != NULL) {printf("%d\t", l->next->data);l = l->next;}printf("\n");
}//按序号查找
node* search1(node* l, int i) {node* p;int j = 0;if (i <= 0) {return NULL;}p = l;while (p->next != NULL && j < i) {p = p->next;j++;}if (i == j) {return p;}else {return NULL;}
}//按值查找
node* search2(node* l, int e) {node* p;p = l->next;while (p != NULL) {if (p->data != e) {p = p->next;}else {break;}}return p;
}//求单链表的长度
int length(node* l) {node* p;p = l->next;int j = 0;while (p != NULL) {p = p->next;j++;}return j;
}//单链表的插入
void enterlist(node* l, int i, int e) {node* p, * s;int k = 0;if (i <= 0) {printf("插入的位置不合理\n");}p = l;while (p != NULL && k < i - 1) {p = p->next;k++;}if (p == NULL) {printf("插入的位置不合理\n");}s = (node*)malloc(sizeof(node));s->data = e;s->next = p->next;p->next = s;printf("插入成功\n");
}//单链表的删除操作
void dellist(node* l, int i) {node* p, * r;int k = 0;p = l;while (p->next != NULL && k < i - 1) {p = p->next;k++;}if (p->next == NULL) {printf("删除位置不合法\n");}r = p->next;p->next = r->next;printf("删除的元素是%d\n", r->data);free(r);
}int main() {node* l, * m;l = (node*)malloc(sizeof(node));startlist(l);printf("单链表已初始化\n");printf("用头插法插入链表l\n");creatfromhead(l);printlist(l);m = (LinkList)malloc(sizeof(node));startlist(m);printf("用尾插法插入链表m\n");creatfromtail(m);printlist(m);printf("查找链表m的第i个结点\n");int k;scanf("%d", &k);node* s;s = search1(m, k);printf("链表中第i个结点的值为:%d\n", s->data);printf("单链表的长度为:%d\n", length(m));printf("在链表m的第一个结点插入2\n");enterlist(m, 1, 2);printlist(m);printf("删除链表m中的第二个结点的值\n");dellist(m, 2);printlist(m);
}
效果图: