数据结构初阶——第三节-- 单链表

链表

  • 1. 链表的概念及结构
    • 1.1 链表的概念及结构
    • 1.2 链表的实现(无头+单向+非循环链表增删查改实现)
      • 1. 动态申请一个节点
      • 2. 单链表打印
      • 3. 单链表尾插
        • 1. 找尾用 tail -> next
      • 4. 单链表尾删 1、没有节点 ,一个节点 2、多个节点
      • 5. 单链表头插
      • 6. 单链表头删 1、没有节点 2.一个节点 3、多个节点
        • 先建立一个头节点,用于保留第二个结点的位置
      • 7. 单链表查找
      • 8. 单链表插入
        • 可以利用7的 查找后 再插入
        • 在pos之前插入 需要遍历
      • 8. 删除pos之后的位置
      • 1.3 总结
        • 1.3.1 传参是传地址(包括指针地址)还是本身取决于,是否要改变传入的值
            • 如果用返回值,那么就传本身就可以 具体看我的博客PTA里的链表例题
          • 链表传入的是实参还是形参,取决于是否需要改变自身,而不用考虑next,
        • 1.3.2 assert用于判断指针是否为 空
        • ***1.3.3 在一个单链表中,在n位置前插入一个x,其实可以在后面插入,然后交换值
  • 2. 链表面试题
    • 1. 删除 val 节点(一直递归直到NULL返回值结束递归,从而回头处理)
      • 1.1 递归思想
        • 1.先直接递归到了最后
        • 2.然后让该结点 等于 下一个递归的返回值
    • 2. 反转一个单链表(通过递归下一个,返回自身,来从后往前处理)
    • 1.1 迭代思想
      • 一个next 接下来要处理的指针
      • 一个cur 是指向当前的
      • 一个prev 是指向前一个的
    • 1.2 递归思想(通过递归下一个,返回自身,来从后往前处理)
      • 1.2.1 还是先通过递归 找到最后(最后几个看题意)
        • 找到倒数第二个,利用 next 的 next 等于 自身,自身等于NULL
      • 1.2.2 通过递归下一个,返回自身,来从后往前处理
    • 3. 链表的中间结点
      • 数组
      • 两次遍历
      • 快慢指针
    • 4. 链表中倒数第k个结点
      • 1. 快慢指针
    • 5. 合并两个有序链表(条件递归)
      • 递归思想
        • 1. 比较两个结点大小,让小的 = 递归
          • 并且此时,接下来返回的值,应该是l1(因为l1小,返回值用于返回上次的递归)
        • 返回值,如果l1是空 那么直接返回剩余的 l2
    • 6. 链表分割
      • 建立两个链表,然后链接
    • 7. 判断链表是否对称
      • 先用快慢指针找中点
    • 8. *****相交链表
      • 哈希表
      • 双指针(*\*\*交替遍历\*\*\*,弥补相差的个数)
    • 9. *****判断环形链表
      • 哈希表
      • 快慢指针(在遍历的时候,出现空则返回false,否则一定能相遇)
      • *****10. 环形链表 II 判断是否为环,且返回环点
      • 哈希表
      • 快慢指针
    • 11. 复制链表(带随机指针)(随机指的点,可能还没出现)
      • A→B→C
      • A→A ′ →B→B ′ →C→C ′
    • 12. 对链表进行插入排序
      • 什么是插入排序?
        • (前面的数据都是已经排序好的,只需比较排序好的后一个元素与前面的元素比较)
    • 13. 删除链表中重复的结点

1. 链表的概念及结构

1.1 链表的概念及结构

// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
数据结构中:数据结构初阶——第三节-- 单链表_第1张图片

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

  1. 单向、双向
  2. 带头、不带头
  3. 循环、非循环数据结构初阶——第三节-- 单链表_第2张图片数据结构初阶——第三节-- 单链表_第3张图片数据结构初阶——第三节-- 单链表_第4张图片

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构数据结构初阶——第三节-- 单链表_第5张图片

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

1.2 链表的实现(无头+单向+非循环链表增删查改实现)

// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

1. 动态申请一个节点

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* node = (SListNode*)malloc(sizeof(SListNode));
	node->data = x;
	node->next = NULL;
 
	return node;
}

2. 单链表打印

void SListPrint(SListNode* plist)
{
	SListNode* cur = plist;
	while (cur)
	//while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

3. 单链表尾插

1. 找尾用 tail -> next

void SListPushBack(SListNode** pplist, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		SListNode* tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
 
		tail->next = newnode;
	}
}

4. 单链表尾删 1、没有节点 ,一个节点 2、多个节点

void SListPopBack(SListNode** pplist)
{
	SListNode* prev = NULL;
	SListNode* tail = *pplist;
	// 1.空、只有一个节点
	// 2.两个及以上的节点
	if (tail == NULL || tail->next == NULL)
	{
		free(tail);
		*pplist = NULL;
	}
	else
	{
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
 
		free(tail);
		tail = NULL;
 
		prev->next = NULL;
	}
}

5. 单链表头插

void SListPushFront(SListNode** pplist, SLTDateType x)
{
	assert(pplist);
 
	// 1.空
	// 2.非空
	SListNode* newnode = BuySListNode(x);
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		newnode->next = *pplist;
		*pplist = newnode;
	}
}

6. 单链表头删 1、没有节点 2.一个节点 3、多个节点

先建立一个头节点,用于保留第二个结点的位置

void SListPopFront(SListNode** pplist)
{
	// 1.空
	// 2.一个
	// 3.两个及以上
	SListNode* first = *pplist;
	if (first == NULL)
	{
		return;
	}
	else if (first->next == NULL)
	{
		free(first);
		*pplist = NULL;
	}
	else
	{
		SListNode* next = first->next;
		free(first);
		*pplist = next;
	}
}

7. 单链表查找

SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	SListNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
			return cur;
 
		cur = cur->next;
	}
 
	return NULL;
}

8. 单链表插入

可以利用7的 查找后 再插入

void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

在pos之前插入 需要遍历

void SListInsertBefore(SLTNode** pplist, SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newnode = BuySLTNode(x);

	if (pos == *pplist) // 头插
	{
		newnode->next = pos;
		*pplist = newnode;
	}
	else
	{
		SLTNode* prev = NULL;
		SLTNode* cur = *pplist;
		while (cur != pos)
		{
			prev = cur;
			cur = cur->next;
		}

		prev->next = newnode;
		newnode->next = pos;
	}
}

8. 删除pos之后的位置

void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	// pos next nextnext
	SListNode* next = pos->next;
 
	if (next != NULL)
	{
		SListNode* nextnext = next->next;
		free(next);
		pos->next = nextnext;
	}
}

1.3 总结

1.3.1 传参是传地址(包括指针地址)还是本身取决于,是否要改变传入的值

如果用返回值,那么就传本身就可以 具体看我的博客PTA里的链表例题

原题链接

链表传入的是实参还是形参,取决于是否需要改变自身,而不用考虑next,

因为,实参和形参的next都是指向的是 实际的地址

1.3.2 assert用于判断指针是否为 空

***1.3.3 在一个单链表中,在n位置前插入一个x,其实可以在后面插入,然后交换值

2. 链表面试题

1. 删除 val 节点(一直递归直到NULL返回值结束递归,从而回头处理)

原题链接
数据结构初阶——第三节-- 单链表_第6张图片
数据结构初阶——第三节-- 单链表_第7张图片

1.1 递归思想

1.先直接递归到了最后

2.然后让该结点 等于 下一个递归的返回值

2. 反转一个单链表(通过递归下一个,返回自身,来从后往前处理)

原题链接
数据结构初阶——第三节-- 单链表_第8张图片
数据结构初阶——第三节-- 单链表_第9张图片

1.1 迭代思想

一个next 接下来要处理的指针

一个cur 是指向当前的

一个prev 是指向前一个的

1.2 递归思想(通过递归下一个,返回自身,来从后往前处理)

1.2.1 还是先通过递归 找到最后(最后几个看题意)

找到倒数第二个,利用 next 的 next 等于 自身,自身等于NULL

1.2.2 通过递归下一个,返回自身,来从后往前处理

3. 链表的中间结点

原题链接
数据结构初阶——第三节-- 单链表_第10张图片
数据结构初阶——第三节-- 单链表_第11张图片

数组

两次遍历

快慢指针

4. 链表中倒数第k个结点

数据结构初阶——第三节-- 单链表_第12张图片
数据结构初阶——第三节-- 单链表_第13张图片

1. 快慢指针

5. 合并两个有序链表(条件递归)

原题链接
数据结构初阶——第三节-- 单链表_第14张图片

数据结构初阶——第三节-- 单链表_第15张图片

递归思想

1. 比较两个结点大小,让小的 = 递归

并且此时,接下来返回的值,应该是l1(因为l1小,返回值用于返回上次的递归)

返回值,如果l1是空 那么直接返回剩余的 l2

6. 链表分割

原题链接
数据结构初阶——第三节-- 单链表_第16张图片
数据结构初阶——第三节-- 单链表_第17张图片

建立两个链表,然后链接

7. 判断链表是否对称

原题链接
数据结构初阶——第三节-- 单链表_第18张图片
数据结构初阶——第三节-- 单链表_第19张图片

先用快慢指针找中点

8. *****相交链表

原题链接
数据结构初阶——第三节-- 单链表_第20张图片

哈希表

双指针(***交替遍历***,弥补相差的个数)

数据结构初阶——第三节-- 单链表_第21张图片

9. *****判断环形链表

哈希表

快慢指针(在遍历的时候,出现空则返回false,否则一定能相遇)

原题链接
数据结构初阶——第三节-- 单链表_第22张图片
数据结构初阶——第三节-- 单链表_第23张图片

*****10. 环形链表 II 判断是否为环,且返回环点

原题链接
数据结构初阶——第三节-- 单链表_第24张图片
数据结构初阶——第三节-- 单链表_第25张图片

哈希表

快慢指针

11. 复制链表(带随机指针)(随机指的点,可能还没出现)

A→B→C

A→A ′ →B→B ′ →C→C ′

原题链接
数据结构初阶——第三节-- 单链表_第26张图片
数据结构初阶——第三节-- 单链表_第27张图片

12. 对链表进行插入排序

什么是插入排序?

(前面的数据都是已经排序好的,只需比较排序好的后一个元素与前面的元素比较)

数据结构初阶——第三节-- 单链表_第28张图片

原题链接
数据结构初阶——第三节-- 单链表_第29张图片
数据结构初阶——第三节-- 单链表_第30张图片

13. 删除链表中重复的结点

原题链接
数据结构初阶——第三节-- 单链表_第31张图片
数据结构初阶——第三节-- 单链表_第32张图片

你可能感兴趣的:(数据结构与算法,c语言)