什么是链表?

struct Node
{
	//next是下一个Node的地址
	struct Node* next;
	//val是这个节点记录的值
	int val;
}

这就是一个链表节点,里面存储了下一个节点的地址和节点值,链表是一个这样的结构
由n个这样的节点关联而成,就像一条锁链
在这里插入图片描述

所以从图形来看,链表是内存不连续(CPU 缓存不友好)的,但是也不会像顺序表(点击传送)那样增加或者删除元素会移动后面的所有元素。

1.单(向)链表

如上图,它就是一条单向链表,我们如果已经拿到了第一个节点,那么我们就可以访问接下来的每个节点,但是我们如果拿到第二个节点,我们就不能去回溯第一个节点,因为next记录的是下一个节点的地址,而没有记录上一个节点的地址,我们并不清楚上一个节点位于内存的何处。

// 遍历链表的函数
void traverseLinkedList(struct Node* head)
{
    struct Node* current = head;
    // 从头节点开始遍历链表
    while (current != NULL)
    {
        // 访问当前节点的值
        printf("%d ", current->val);
        // 将指针移动到下一个节点
        current = current->next;
    }
}

2.双向链表

在这里插入图片描述
我们期望实现这样的链表,让我们可以去访问一个节点之前的,和之后的节点

struct Node
{
	//next是下一个Node的地址
	struct Node* next;
	//last是上一个节点的地址
	struct Node* last;
	//val是这个节点记录的值
	int val;
}

我们需要记录上一个和下一个节点的地址,才能向前后回溯。

3.(单向)循环链表

如果单向链表和双向链表可以比作一条锁链的话,那我们只需把首尾接起来,就是一条循环链表
在这里插入图片描述

// 遍历循环链表的函数
void traverseCircularLinkedList(struct Node* head)
{
    if (head == NULL)
    {
        printf("Empty circular linked list.\n");
        return;
    }

    struct Node* current = head;
    do
    {
        // 访问当前节点的值
        printf("%d ", current->val);
        // 将指针移动到下一个节点
        current = current->next;
    } while (current != head); // 当回到起始节点时停止遍历
}

链表的优缺点

1.元素数量变更容易
如果我们想要增加一个节点,那么我们只需要把这个链条切断,然后把新的节点接进去
在这里插入图片描述
在这里插入图片描述
2.查找困难
如果我们现在需要找到某个节点,哪怕我们知道它在哪个位置,我们也只能迫不得已的逐个遍历,例如当前持有节点位置为0(绿色的),要取得往后的第三个节点(红色的),那么就需要一步一步的向后移动。如果我们不知道红色在哪,就要挨个比较,直到找到红色,

也就是说,需要通过遍历来查找。

如果我们(绿色)是在中间的位置,且为双向链表,查找红色甚至还需要两个方向分别交替的查找。

在这里插入图片描述
为了克服这个问题,我们引入一种新的链表

4.静态链表

  • 创建一个大小是n的数组,类型为链表节点,不需要处理顺序
  • 链表节点存储下一个节点的数组下标或者上一个节点的数组下标

这样做有非常大的好处

  1. 能根据一个节点直接找到上或者下一个节点
  2. 能根据数组轻松遍历或者无序访问一个链表
  3. 在n元素数量内保留了链表轻松变得节点关系,插入,删除比较高效

缺点是:
4. 多用了一个数组的空间
5. 超过n的扩容受到数组容量的限制,速度和空间应用顺序表的性质。

在这里插入图片描述

在这里插入图片描述

// 定义静态链表节点
struct StaticNode
{
    int data; // 存储节点的数据
    int next; // 存储下一个节点的索引
};

// 静态链表的最大容量
#define MAX_SIZE 100

int main()
{
    // 创建静态链表数组
    struct StaticNode staticList[MAX_SIZE];
    
    // 向静态链表中插入数据
    staticList[0].data = 1;
    staticList[0].next = 1;

    staticList[1].data = 2;
    staticList[1].next = 2;

    staticList[2].data = 3;
    staticList[2].next = -1; // 最后一个节点的下一个节点索引为-1


    return 0;
}

链表插入


struct Node
{
	//next是下一个Node的地址
	struct Node* next = NULL;
	//val是这个节点记录的值
	int val = 0;
}

//在一个节点后面插入节点
void InsertNode(Node* node,Node* nNode)
{
	Node* next = node->next;
	if(next==NULL)
	{
		node->next = nNode;
		return;
	}
	node->next = nNode;
	nNode->next = next;
}

链表移除


struct Node
{
	//next是下一个Node的地址
	struct Node* next = NULL;
	struct Node* last = NULL;
	//val是这个节点记录的值
	int val = 0;
}

//移除这个节点,返回下一个位置的节点,如果在末尾返回NULL
Node* DeleteNode(Node* node)
{
	Node* last = node->last;
	Node* next = node->next;
	last->next = next;
	next->last = last;
	//释放内存
	delete node;
	return next;

}

合并链表

struct Node
{
	//next是下一个Node的地址
	struct Node* next = NULL;
	//val是这个节点记录的值
	int val = 0;
}

//给出两个链表的头节点
//另一种实现可以是给尾节点,就没法返回头了
Node* MergeLinkedList(Node* node1,Node* node2)
{
	Node* current = node1;
	while(current->next != NULL)
	{
		current = node1->next;
	}
	current->next = node2;
	return node1;
}

交叉合并

在上文的InsertNode的基础上

void CrossMerge(struct Node* list1, struct Node* list2)
{
    if (list1 == NULL || list2 == NULL)
    {
        return; // 如果其中一个链表为空,无法合并
    }

    struct Node* current1 = list1;
    struct Node* current2 = list2;

    while (current1 != NULL && current2 != NULL)
    {
        struct Node* temp1 = current1->next;
        struct Node* temp2 = current2->next;

        current1->next = current2;
        current2->next = temp1;

        current1 = temp1;
        current2 = temp2;
    }
}