【数据结构】线性表-线性表的链式表示与实现-有序链表

什么是链表?

链表就是由很多个结点构成的线性结构,每个结点里面存储着本身需要存储的数据和下一个数据的存放地址,可以理解为套娃,A可以找到B,B可以找到C,C可以找到D...

下图为线性链表,还有循环链表和双向链表,后续我会讲解。

 

一、线性链表的数据对象LNode和*LinkList

线性链表的数据对象特别简单,就是存储的数据和存储下一个结点的地址,因此数据对象如下:

  1. 数据:data
  2. 存储下一个结点的地址:*next

为什么要创建指针结点?

因为生成节点需要为结点用malloc动态分配内存,需要指针去接收分配内存的首地址

 

typedef int ElemType;

typedef struct LNode {
	ElemType data;  //存储数据
	struct LNode* next;  //存储下一个结点的地址
}LNode, *LinkList;  //指针用于分配内存

二、动态生成线性表链表CreateList_L

分析:

1. 为头结点L申请内存,头结点中没有数据,且*next为NULL

 

2. 创建一个新的结点P,并为结点P赋值上数据

 

3. 让新结点P的*next指向NULL,头结点L的*next指向P。

但是别急,我们多分析两个节点,如果现在我们用上述方法接着往后插入节点,我们就需要让上一个结点的*next指向新的结点,我们要找到前一个结点。这个时候我们就要用一个变量记录一共有了多少个结点,然后不断循环让指针指到上一个结点,太麻烦了。看接下来的操作即可。

4. 我们创建第二个结点P

5. 这一步就像A->B,变成A->C->B,原来A指向的B,现在把A的指向给C就能实现C->B,再让A的指向指向C,就能实现A->C->B。

原来L的*next指向data1,现在把L的指向给data2,就实现了data2的*next指向data1,再让L的*next指向data2,就能实现L->data2->data1。而这个过程中data1的*next始终为NULL,插入方法就是将结点插入到头结点之后,而第一个进去的结点始终是最后一个结点。

 

6. 我们接着来创建第三个结点

 

7. 调整指向

 

8. 完成

 

总结:

 

//动态创建链表
void CreateList_L(LinkList& L, int n) {
	//创建头结点
	L = (LinkList)malloc(sizeof(LNode));
	L->next = NULL;  //头结点的next为NULL
	for (int i = 0; i < n; i++) {  //遍历存入输入
		LinkList P = (LinkList)malloc(sizeof(LNode));  //创建新节点P
		scanf("%d", & P->data);  //输入P的数据
		P->next = L->next;  //P的next为L的next
		L->next = P;  //头结点L的next指向新结点
	}
}

三、访问元素

现在我们想要拿到位置为3的元素,首先我们要定义一个指针指向第一个元素,也就是L的*next。然后循环遍历i-1次也就是P的指向从1到3,就可以让P指向下标为3的位置。最后读取P的数据就行。

 

总结:

 

//查找元素
bool GetElem_L(LinkList L, int i, ElemType &e) {
	//定义一个指针指向头结点
	LinkList P = L;
	int j = 1;
	while (P && j < i) {  //如果P不为空且查找P指向下标不为n
		P = P->next;  //P指向下一个结点
		j++;
	}
	if (!P || j > i) return false;  //如果上面遍历结束P为空(n超出范围)或者查找超出指定范围则返回false
	e = P->data;  //否则返回P指向i结点的数据
	return true;
}

四、插入元素

我们以在位置3插入data4为例子

分析:

定义一个指针P,指向头结点,P的作用是指向插入结点的上一个结点,做链接工作。 

 

1. 循环i-2次,让指针P指向i的前一个结点,也就是要删除的结点

 

2. 创建一个新的结点S,并为其data赋值上data4

 

3. 按照新建结点的步骤插入结点

 

4. 完成

 

总结:

 

//插入结点
bool ListInsert_L(LinkList L, int i, ElemType e) {
	//定义指针P指向第一个结点
	LinkList P = L;
	int j = 1;
	while (P && j < i-1) {  //在P不为空的情况下遍历n-2次
		P = P->next;  //最终P指向i-1位置的结点
		j++;
	}
	if (!P || j > i) return false;  //不符合条件,插入失败
	LinkList S = (LinkList)malloc(sizeof(LNode));  //为新节点分配空间
	S->data = e;  //为新节点赋data值
	//将结点S接入到链表中
	S->next = P->next;  
	P->next = S;
	return true;
}

五、删除元素ListDelete_L

以删除第三个结点为例

分析:

1. 定义指针P,指向头结点,遍历到i-1个元素,这一步和插入结点的(1)(2)步操作相同

 

2. 定义指针q为P的*next,那么q的*next就指向的是data4

 

3. 让P的*next指向data4结点,也就是把q的*next赋值给P的*next。

4. 最后free掉q就完事儿了

 

总结:

//删除结点
void ListDelete_L(LinkList& L, int i, ElemType &e) {
	//获取第i-1个结点
	LinkList P = L;
	int j = 1;
	while (P && j < i) {
		P = P->next;
		j++;
	}
	LinkList q = P->next;  //q为删除的结点
	e = q->data;  //把删除的数据拿走
	P->next = q->next;  //结点i-1和结点i+1连接起来
	free(q);  //释放删除节点
}

六、归并有序链表MergeList_L

已知两个元素按值非递减排列的单链线性表La = 3,5,8,11,Lb = 2,6,8,9,11,15,20

现将La和Lb归并得到新的单链线性表Lc,Lc的元素也按值非递减排列

分析:

1. 定义pa指向La的第一个结点,pb指向Lb的第一个节点。把La的头结点作为Lc的头结点,pc指向Lc的头结点(也就是La的头结点)

2. 比较pa结点和pb结点的大小,让pc的*next指向小的结点,也就是把小的结点接入到Lc中。

3. 将pc移动到pb(元素小的结点)的位置,pb向后移动一位

4. 接着比较pa结点和pb结点的大小,让pc的*next指向小的结点

5. 将pc移动到pa(元素小的结点)的位置,pa向后移动一位 

 

6. 接着4,5步的内容 

 

 7. 当一直执行如上操作直到La和Lb有一个结束时(即pa或者pb指向NULL),结束判断循环

8. 将剩下的部分接入Lc中,并释放Lb

9. 我们整理一下上图,红色箭头就是Lc中*next的指向

总结:

 

 

//归并两个链表
void MergeList_L(LinkList La, LinkList Lb, LinkList& Lc) {
	LinkList pa = La->next; 
	LinkList pb = Lb->next;
	LinkList pc = Lc = La;
	while (pa && pb) {  //pa或者pb不为NULL
		//判断pa结点和pb结点的大小
		if (pa->data > pb->data) {  //如果pb结点比pa结点小
			pc->next = pb;  //将小的结点接入Lc
			pc = pb;  //pc指向小的结点
			pb = pb->next;  //pb向后移
		}
		else {  //如果pa结点比pb结点小
			pc->next = pa;
			pc = pa;
			pa = pa->next;
		}
	}
	pc->next = pa ? pa : pb;  //将剩下的结点接入Lc中
	free(Lb);  //释放Lb的头结点
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值