单链表的基础操作(带头结点)

链表(带头结点)的基础操作:(王道书的实现,附带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可以问我)写的有点丑,大家凑活着看

  • 12
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自律的光电人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值