链表(带头结点)的基础操作:(王道书的实现,附带main函数)
1.链表的初始化
2.头插法建立单链表
3.尾插法建立单链表
4.指定结点的前插
5.指定结点的后插
6.按位查询
7.按值查询
8.链表的长度
9.头插法逆置单链表
10.三指针法逆置单链表
11.La和Lb链表合并为Lc
12.删除指定结点
13.查找链表中间结点(快慢指针法)
14.在单链表中查找倒数第k个结点
15.删除单链表中的重复元素
16.打印单链表
/*单链表:每个结点除了存放数据元素外,还要存储指向下一个元素的指针
优点:不要求大片连续空间,改变容量方便
缺点:不可随机存储,要消耗一定空间存放指针
*/
//1.带头结点的单链表
//结构体定义
/**/
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
typedef struct LNode { //定义单链表结点类型
ElemType data; //每个节点存放一个数据元素
struct LNode* next; //指针指向下一个结点
}LNode,*LinkList;
//1.初始化一个单链表(带头结点)
bool InitList(LinkList& L) {
L = (LNode*)malloc(sizeof(LNode));
if (L == NULL) //内存不足,分配失败
return false;
L->next = NULL; //头结点之后暂时还没有结点
return true;
}
//2.头插法建立单链表
LinkList List_HeadInsert(LinkList& L) { //逆向建立单链表
LNode* s;
int x;
L = (LinkList)malloc(sizeof(LNode));//创建头结点
L->next = NULL; //初始化空链表
printf("请输入链表:\n");
scanf_s("%d", &x); //输入结点的值
while (x != 9999) {
s = (LNode*)malloc(sizeof(LNode));//创建新的结点
s->data = x;
s->next = L->next;
L->next = s; //将新结点插入表中,L为头指针
scanf_s("%d", &x);
}
return L;
}
//3.尾插法建立单链表
LinkList List_TailInsert(LinkList& L) {
int x;
L = (LinkList)malloc(sizeof(LNode)); //建立头结点
LNode* s, * r = L; //r为表尾指针
printf("请输入链表:\n");
scanf_s("%d", &x); //输入结点的值
while (x != 9999) { //输入9999表示结束
s = (LNode*)malloc(sizeof(LNode));
s->data = x;
r->next = s; //在r结点后插入元素x
r = s; //r指向新的表尾结点(永远保持r指向最后一个结点)
scanf_s("%d", &x);
}
r->next = NULL; //表尾指针置空
return L;
}
//3.前插操作:在p结点之前插入结点s
bool InsertPriorNode(LNode* p, int e) {
if (p == NULL) { //判断p指针是否指向空
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode)); //创建一个新结点
if (s == NULL) {//内存分配失败
return false;
}
s->next = p->next;
p->next = s; //新结点s连接到p之后
s->data = p->data; //将p结点中的值复制到s的结点中
p->data = e; //p中元素覆盖为e
return true;
}
//王道书版本的前插操作
bool InsertPriorNode1(LNode* p, LNode* s) {
if (p == NULL || s == NULL) {
return false;
}
s->next = p->next;
p->next = s; //连接p和s
int temp = s->data; //设置中间变量暂存s->data,交换数据域部分
s->data = p->data;
p->data = temp;
return true;
}
//4.后插操作
//实现步骤:1.循环找到第i-1个结点。2.在该结点进行后插操作
//(1)在第i个位置插入元素e(带头结点)
bool InsertNextNode(LNode* p, int e) {
if (p == NULL) {
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));//创建新结点
if (s == NULL) { //内存分配失败
return false;
}
s->data = e;//用s结点保存数据e
s->next = p->next;
p->next = s; //将结点s连接到p之后
return true;
}
//5.指定节点的后插操作
bool ListInsert(LinkList &L ,int i,int e) {
if (i < 1) {
return false;
}
LNode* p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点(不存数据)
while (p != NULL && j < i - 1) {
p = p->next;//指针p依次往后遍历链表中的元素
j++;
}
return InsertNextNode(p, e);
}
//6.按位查询
//注:强调这是一个单链表 --使用LinkList。强调这是一个结点 --使用LNode*
LNode* GetElem(LinkList L, ElemType i) {
int j = 1;
LNode* p = L->next;
if (i == 0)
return L;
if (i < 1)
return NULL;
while (p != NULL && j < i) {
p = p->next;
j++;
}
return p;
}
//7.按值查找
LNode* LocateElem(LinkList L, ElemType e) {
LNode* p = L->next;
//int i = 0;
//从第1个结点开始查找数据域为e的结点
while (p != NULL && p->data != e){
p = p->next;
//i++;
}
return p; //找到后返回该结点指针,否则返回NULL
}
//8.求表的长度
int Length(LinkList L) {
int len = 0; //统计表长
LNode* p = L;
while (p->next != NULL) {
p = p->next;
len++;
}
return len;
}
//9.头插法逆置单链表
LinkList Reverse_1(LinkList L) {
LNode* p, * r;
//LinkList p,r;
//循环前操作
p = L->next;
L->next = NULL;
//循环操作
while (p != NULL) {
r = p->next;
p->next = L->next;
L->next = p;
p = r;
}
return L;
}
//10.三指针法逆置单链表(pre,p,r),r作工作指针,暂存结点地址
LinkList Reverse_2(LinkList L) {
LinkList pre, p, r;
//循环前操作
p = L->next;
r = p->next;
p->next = NULL;
//循环操作
while (r != NULL) {
pre = p;
p = r;
r = r->next;
p->next = pre;
}
L->next = p;
return L;
}
//11.有序链表的合并
//注:链表必须有序!
void MergeLinkList(LinkList& La, LinkList& Lb, LinkList& Lc) {
LinkList p, q, r;
p = La->next;
q = Lb->next;
Lc = La;
r = Lc;
while (p && q) {
if (p->data <= q->data) {
r->next = p;
r = p;
p = p->next;
}
else {
r->next = q;
r = q;
q = q->next;
}
}
//如果p不空,则把p后面剩余节点链表接起来,即r->next=p;否则r->next=q;
r->next = p ? p : q; //相当于if(p) r->next=p; else r->next=q;
delete Lb;
}
//按位序删除(带头结点)
// 删除表L中第i个位置的元素,并用e返回删除元素的值。
//步骤:1.循环找到第i-1个结点。2.删除第i个结点
//时间复杂度O(n)
bool ListDelete(LinkList& L, int i, ElemType& e) {
if (i < 1)
return false;
//LNode* p = GetElem(L,i-1);
LNode* p; //创建一个指针p
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点(不存数据)
while (p != NULL && j < i - 1) { //循环找到第i-1个结点
p = p->next; //指针p每次移动一个元素
j++;
}
if (p == NULL) { //判断p指针指向的链表元素是否为空
return false;
}
if (p->next == NULL) { //若i-1个结点之后其他结点
return false;
}
LNode* q = p->next;//创建一个q指针指向p的后继
e = q->data; //把q的data赋值给e
p->next = q->next; //p与q的后继相连,即断开q
free(q); //释放掉q结点
return true;
}
//12.删除指定结点p
bool DeleteNode(LNode* p) {
if (p == NULL) { //如果p指向的元素是空的话
return false;
}
LNode* q = p->next; //创建指针q指向p的后继元素
//下面一条代码:是和后继结点交换数据域,这里存在一个问题,如果p是最后一个结点,他没有后继结点,不能交换数据域
p->data = p->next->data;
p->next = q->next; //将q结点从连接中断开
free(q); //释放q结点
return true;
}
//13.查找链表中间结点(快慢指针法)
//思路:设置一个快指针p,每次走两步,一个满指针q,每次走一步,
//当快指针走到表尾时,此时慢指针即为中间结点。
LinkList FindMiddle(LinkList L) {
LinkList p, q;//p为快指针,q为慢指针
p = L;
q = L;//p和q开始都指向头结点
//循环条件解读,大家可以尝试着画一下,不懂私信我
//1.偶数单链表,p=NULL结束
//2.奇数单链表,p->next=NULL结束。
while (p != NULL && p->next != NULL) {
p = p->next->next; //快指针每次走两步
q = q->next; //慢指针每次走一步
}
return q;//返回中间结点的指针
}
//14.补充,如何在单链表中查找倒数第k个结点?
//思路:快慢指针法,慢指针q先不动,快指针p先走k-1步,然后两只真一起以同样的速度走。
//当快指针走到表尾时,慢指针刚好停留在倒数第k个结点
//注:此时的慢指针和快指针都是一次走一步
LinkList Find_reverse_k(LinkList L, int k) {
LinkList p, q;
p = L->next; //p为快指针
q = L->next; //q为慢指针
while (p->next != NULL) {
if (--k <= 0) //当k减到0是,慢指针q开始走
q = q->next;
p = p->next;
}
if (k > 0)//k不合法,k不能大于链表的长度
return NULL;
else
return q;//返回倒数第k个结点
}
//15.删除单链表中的重复元素
void Delete_Repeate_Elem(LinkList& L) {
LinkList p, q;
int x;
int n = 10;
int* flag = new int[ n + 1];//定义flag数组,分配n+1个空间,0空间不用。
for (int i = 0; i < n + 1; i++) { //初始化flag数组,每个元素都为0
flag[i] = 0;
}
p = L;//p指向头结点
while (p->next != NULL) {
x = abs(p->next->data);
if (flag[x] == 0) {
flag[x] = 1;//标志出现过的元素
p = p->next; //p指针每次后移一位
}
else {//出现重复
q = p->next;
p->next = q->next;//普通的链表删除操作
//free(q);
delete q;
}
}
delete[]flag;
}
//16.打印单链表
void PrintList(LinkList L) {
LinkList p; //创建一个指针p
p = L->next; //p指向头结点
printf("链表为:\n");
while (p != NULL) { //判断p是否为空
printf("%d ", p->data); //依次输入p的数据
p = p->next; //指针p每循环一次指向下一个结点元素
}
printf("\n");
}
int main() {
//LNode* p = L->next;
//LNode* s = NULL;
// LNode* p = (LNode*)malloc(sizeof(LNode));
LinkList L, La, Lb, Lc;
InitList(L);
int command;
while (1) {
printf("*******************************\n");
printf("下面是操作界面\n");
printf("1.链表的初始化 2.头插法建立单链表\n");
printf("3.尾插法建立单链表 4.指定结点的前插\n");
printf("5.指定结点的后插 6.按位查询\n");
printf("7.按值查询 8.链表的长度\n");
printf("9.头插法逆置单链表 10.三指针法逆置单链表\n");
printf("11.La和Lb链表合并为Lc 12.删除指定结点\n");
printf("13.查找链表中间结点(快慢指针法) 14.在单链表中查找倒数第k个结点\n");
printf("15.删除单链表中的重复元素 16.按位序删除 \n");
printf("17.退出 \n");
printf("*******************************\n");
printf("请输入您要进行的操作:\n");
scanf_s("%d", &command);
switch (command)
{
case 1:
//1.链表的初始化
if (InitList(L) == 1) printf("初始化成功!\n");else printf("初始化失败!\n");
break;
case 2:
//2.头插法建立单链表
List_HeadInsert(L);
break;
case 3:
//3.尾插法建立单链表
List_TailInsert(L);
break;
case 4:
//4.指定结点的前插前插
printf("进行前插操作(在第二个位置前插8):\n");
InsertPriorNode(L->next->next, 8);
break;
case 5:
//5.指定结点的后插操作
printf("进行后插操作(在第2个位置后插5):\n");
ListInsert(L, 3, 5);
//前插(王道书版本)
//InsertPriorNode1(p, s);
//按位序删除
//ListDelete(L, 4, e);
//printf("删除的元素为:%d\n",e);
break;
case 6:
//6.按位查询
int i;
printf("请输入要查结点的位次:\n");
scanf_s("%d", &i);
printf("按位查找的结点值为:%d\n", GetElem(L, i)->data);
printf("按位查找的结点地址为:%d\n", GetElem(L, i));
break;
case 7:
//7.按值查询
int e;
printf("请输入要查结点的值:\n");
scanf_s("%d", &e);
printf("按值查找的结点的地址为:%d\n", LocateElem(L, e));
break;
case 8:
//8.链表长度
printf("链表的长度为%d\n", Length(L));
break;
case 9:
//9.头插法逆置单链表
Reverse_1(L);
break;
case 10:
//10.三指针法逆置单链表
Reverse_2(L);
break;
case 11:
//11.链表La和Lb的合并操作
InitList(La);
InitList(Lb);
InitList(Lc);
//尾插法建立单链表La,Lb
printf("尾插法建立La");
List_TailInsert(La);
PrintList(La);
printf("尾插法建立Lb");
List_TailInsert(Lb);
PrintList(Lb);
printf("进行链表La和Lb的合并操作:\n");
MergeLinkList(La, Lb, Lc);
PrintList(Lc);
break;
case 12:
//12.指定结点删除
DeleteNode(L->next);
PrintList(L);
break;
case 13:
//13.查找链表中间结点(快慢指针法)
printf("链表的中间结点的值为:%d\n", FindMiddle(L)->data);
printf("链表的中间结点的地址为:%d\n", FindMiddle(L));
break;
case 14:
//14.补充,在单链表中查找倒数第k个结点
int k;
printf("请输入要查找倒数第几个结点:\n");
scanf_s("%d", &k);
printf("倒数第%d个结点的值为%d\n",k,Find_reverse_k(L, k)->data);
//printf("倒数第%d个结点的地址为%d\n", k, Find_reverse_k(L, k));
break;
case 15:
//15.删除单链表中的重复元素
Delete_Repeate_Elem(L);
break;
case 16:
//16.按位序删除(带头结点)
// 删除表L中第i个位置的元素,并用e返回删除元素的值。
int x1,z;
printf("请输入要删除元素的位置:\n");
scanf_s("%d", &z);
ListDelete(L,z,x1);
printf("删除的元素为:%d\n",x1);
break;
case 17:
//17.退出
break;
default:
printf("输入错误\n");
break;
}
PrintList(L);
printf("\n");
}
return 0;
}
我来带大家运行一下上面我写的代码
1.首先我们必须要初始化滴
2.然后头插法(倒序)建立单链表(注:头插法和尾插法输入元素都是9999结束哈,忘记写了)
3.试一下尾插法(正序)倒序建表
4.前插操作,这里我在main函数中指定的是在第二个元素前插一个8
InsertPriorNode(L->next->next, 8);
5.后插操作,这里是在第3个位置上插入5(注:因为带头结点,所以函数中的第三个位置其实是实际的第二个位置)
ListInsert(L, 3, 5);
6.按位查询,可以查询到结点的地址,同样也可以获得结点的值。
7.按值查询,可以查询到结点的地址
8.查看链表的长度
9.头插法逆置单链表
10.三指针法逆置单链表
11.La和Lb链表合并为Lc
注意嗷,你的输入的La和Lb必须有序
我再注:这里有L是因为我在循环外放了个PrintList(L),这样就不用每一次小情况都单独加一个 PrintList(L)了,太懒了 哈哈哈
12.删除指定结点(我这里在main函数中写的是删除第一个数据结点(头结点后面那个))
注:为什么会输出两次删除指定结点后的呢,是因为我在case12里多加了个PrintList(L),尴尬了,凑合着看吧,我一会要去背单词了没时间改了。
后面是补充的:
13.查找链表中间结点(快慢指针法)
14.在单链表中查找倒数第k个结点
15.删除单链表中的重复元素
16.按位序删除元素,(即删除第z个位置的元素并用x1返回删除的元素值)
最后啊,这是头插法逆置和三指针法逆置的图解(看不懂或者看不清的加我QQ2725271785可以问我)写的有点丑,大家凑活着看