关于链表,看这一篇就够了!(新手入门)

链表是一种物理存储单元上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

线性表的顺序存储结构缺点是每一次插入和删除元素,大量元素的移动会导致时间效率低下。为了改进顺序存储结构的缺点,引入链式存储结构,即为链表。

链式存储结构的特点是用一组任意的存储单元来存储线性表中的数据元素。这样在插入和删除元素时,可以通过直接修改指针完成操作,时间效率大大提高。但因为链式存储结构的存储单元不连续,所以需要通过指针来访问它的后续元素。

为了表示每个数据元素与其直接后继数据元素之间的逻辑关系,我们需要存出一个其直接后继的存储位置。我们把存储数据元素信息的域成为数据域,把存储后继位置的域称为指针域,这两部分构成一个节点。

n个节点链接成一个链表,即为线性表的链式存储结构。因为每个节点只有一个指针域,所以又将这样的链表称为单链表。

下面介绍单链表的几种基本操作:

链表的创建

链表的一个节点由指针域数据域构成。

链表的整体思想其实并不难懂,但一旦让他和指针结合在一起时,就容易让人摸不得头脑。

可以这样理解:在链表的创建中,添加一个指向下一个节点的指针,这个指针保存的是下一个节点的地址,我们说这个指针指向下一个节点

那么指针的类型是什么呢?当然是Node了,因为指针不仅仅指向数据域,同时也指向指针域。

下面引入一段话来帮助理解:

将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。

代码实现:

struct Node{
   
    ElemType data;
    struct Node *next;
};

链表的初始化

  1. 参数的传入:涉及改变链表的操作统统用指针传链表,不然函数调用完成之后,为传入的链表分配的的内存会自动释放,链表不会有任何改变。
  2. 创建头结点,为头结点分配内存。
  3. 令头节点的指针为空指针(指针不初始化容易出现很多问题)

PS:这里为什么要动态分配内存呢?

因为这就是数组和链表的区别呀:线性表的顺序存储结构用数组实现,而数组占用的是一整块内存,数组元素分布很集中,需要提前预定数组的长度。

链表是一种动态结构,它所占用的大小和位置是不需要提前分配的,可以根据自身的需求即时生成。

代码实现:

void InitList(LinkList *L){
   
	*L = (LinkList)malloc(sizeof(Node));
	(*L)->next = NULL;
} 

判断链表是否为空

如果链表头结点指针域不为空,证明链表不为空。反之链表为空。(因为头结点指针域存储的就是链表的第一个元素的地址)

bool EmptyList(LinkList L){
   
	if(L->next)
		return false;
	else
		return true;
}

返回链表元素个数

因为链表中没有定义表长,所以要用到“工作指针后移”的思想,就是从第一个节点指针开始依次后移,直到节点为空为止,这时循环执行的的次数就是表长。

  1. 声明一个节点指针 p 指向链表的第一个节点。
  2. 当 p 不为空时,使指针 p 不断后移。
  3. 用 i 计数,循环结束后返回。

代码实现:

int LengthList(LinkList L){
   
	int i = 0;
	LinkList p = L->next;
	//L是头结点,L->next 代表链表的第一个节点。
	//LinkList其实是 Node * ,所以p指向链表的链表的第一个节点。
	while(p){
   
		i++;
		p = p->next;
	}
	//指针一直后移,直到节点不存在为止。
	return i;
}

清空链表

这里仍然用到了“工作指针后移”的思想,从第一个节点开始,每一个节点依次释放内存,直到最后一个节点停止。

  1. 声明节点q,p。
  2. 将第一个节点赋值给p。
  3. 将下一个节点赋值给q,释放q,将q赋值给q。
  4. 往复循环,直到全部节点的内存释放完成。

代码实现:

void ClearList(LinkList *L){
   
	LinkList p,q;
	p = (*L)->next;
	while(p){
   
		q = p->next;
		free(p);
		p = q;
	}
	(*L)->next = NULL;
}

思考:while循环里的值能不能为:

free(p);
p = p->next;

答:肯定会出错。

因为这里的free释放的是 p 整个节点,指针域也会消失。指针域消失后运行第二行代码时会报错。

返回给定位置的元素值

节点指针从第一个节点开始后移,直到指针移动到到指定位置时,若节点不为空,直接返回其值。

  1. 创建一个节点指针p指向链表的第一个节点,初始化cnt从1开始
    (为什么从1开始,因为位置最小是1)
  2. 当 cnt < i 时,遍历链表,使p的指针不断后移
  3. 当 cnt = i 时,返回该节点数据域的值。
  4. 如果链表末尾p为空,则说明第i个元素不存在

代码实现:

Status(LinkList L,int i,Elemtype *e){
   
	int cnt = 1;
	LinkList p = L->next;
	while(p && cnt < i){
   
		p = p->next;
		cnt++; 
	}
	if(!p)
		return ERROR;
	*e = p->data;
	return OK;
}

查找数据

结合“工作指针后移”的思想,以下的代码应该不难理解。

  1. 声明一个节点 p 指向链表的第一个节点,初始化 i 从0开始。
  2. 依次对每一个 p 节点的指针域与 e 进行对比,相等则返回对应值,不相等则返回0。
  3. 当 p 不为空时,使 p 指针不断后移。

代码实现:

int LocateList(LinkList L,Elemtype *e){
   
	int i = 0;
	LinkList p = L->next;
	
	while(p){
   
		i++;
		if(p->data == e)
			return i;
			
		p = p->next;	
	}
	return 0;
}

删除元素

节点指针依次后移,到指定位置后,如果节点不为空,返回其数据域。释放该节点前,要将前一个节点的指针指向该删除节点的后继元素。

  1. 声明一节点 p 指向链表的头结点,初始化 cnt 为 1。
  2. 当 cnt 小于 i 时,遍历链表,然p的指针不断后移,不断指向下一个节点,cnt 逐次加1。
  3. 如果链表末尾 p 不存在,说明要查找的元素不存在。
  4. 将要删除的节点赋值给 q。
  5. 将 q 的后继赋值给 p 的后继。
  6. 返回 q 的数据域给 e。
  7. 系统回收 q 节点,释放q的内存。

代码实现:

Status ListDelete(LinkList *L,int i,ElemType *e){
   
	int cnt = 1;
	LinkList q,p;
	p = (*L);
	//此时 p 为头节点,p->next为第一个节点,对应cnt的值为1。
	while( p->next && cnt < i){
   
		p 
  • 636
    点赞
  • 3254
    收藏
    觉得还不错? 一键收藏
  • 51
    评论
以下是用C语言实现奇偶链表的代码和注释: ```c #include <stdio.h> #include <stdlib.h> // 定义链表节点结构体 typedef struct ListNode { int val; struct ListNode *next; } ListNode; // 创建链表函数,返回链表指针 ListNode* createList() { ListNode *head = (ListNode*)malloc(sizeof(ListNode)); // 创建头节点 head->next = NULL; // 头节点的next指针指向NULL ListNode *tail = head; // 定义尾指针,初始指向头节点 int data; scanf("%d", &data); while (data != -1) { // 输入-1表示链表输入结束 ListNode *cur = (ListNode*)malloc(sizeof(ListNode)); // 创建新节点 cur->val = data; // 新节点的值为输入的值 cur->next = NULL; // 新节点的next指针指向NULL tail->next = cur; // 尾指针的next指针指向新节点 tail = cur; // 尾指针指向新节点 scanf("%d", &data); } return head; // 返回头指针 } // 分离奇偶链表函数,返回新链表指针 ListNode* oddEvenList(ListNode* head) { if (head == NULL || head->next == NULL) { // 如果链表为空或只有一个节点,直接返回原链表指针 return head; } ListNode *odd = head; // 定义奇数节点指针,初始指向头节点 ListNode *even = head->next; // 定义偶数节点指针,初始指向第二个节点 ListNode *evenHead = even; // 定义偶数链表指针,初始指向第二个节点 while (even != NULL && even->next != NULL) { // 遍历链表,将奇数节点和偶数节点分别连接起来 odd->next = even->next; // 奇数节点的next指针指向下一个奇数节点 odd = odd->next; // 奇数节点指针指向下一个奇数节点 even->next = odd->next; // 偶数节点的next指针指向下一个偶数节点 even = even->next; // 偶数节点指针指向下一个偶数节点 } odd->next = evenHead; // 将奇数链表和偶数链表连接起来 return head; // 返回原链表指针 } int main() { ListNode *head = createList(); // 创建链表 head = oddEvenList(head); // 分离奇偶链表 ListNode *cur = head->next; // 定义指针,指向第一个节点 while (cur != NULL) { // 遍历链表,输出每个节点的值 printf("%d ", cur->val); cur = cur->next; } return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值