用C实现单向链表

253 阅读3分钟

链表的定义

链表的实现

通用头文件 fatal.h

#include <stdio.h>
#include <stdlib.h>

#define Error(Str) FatalError(Str)
#define FatalError(Str) fprintf(stderr, "%s\n", Str), exit(1)

链表结构及方法声明头文件 list.h

typedef int ElementType;

#ifndef _List_H
#define _List_H

struct Node;
typedef struct Node *PtrToNode;
typedef PtrToNode List;
typedef PtrToNode Position;

List MakeEmpty(List L);
int IsEmpty(List L);
int IsLast(Position X, List L);
Position Find(ElementType X, List L);
void Delete(ElementType X, List L);
Position FindPrevious(ElementType X, List L);
void Insert(ElementType X, List L, Position P);
void DeleteList(List L);
Position Header(List L);
Position First(List L);
Position Advance(Position P);
ElementType Retrieve(Position P);

#endif

链表结构的实现 list.c

#include "list.h"
#include <stdlib.h>
#include "fatal.h"

struct Node
{
	ElementType Element;  // 节点数据
	Position Next; // 指向下一个链表节点的指针
};

// 构造一个新的空链表
List
MakeEmpty(List L)
{
	if (L != NULL)
		DeleteList(L);
	L = malloc(sizeof(struct Node));
	if (L == NULL)
		FatalError("Out of memory!");
	L->Next = NULL;
	return L;
}

// 测试是否是一个空链表
int
IsEmpty(List L)
{
	return L->Next == NULL;
}

// 测试是否非空链表的最后一个有效元素
int
IsLast(Position P, List L)
{
	return P->Next == NULL;
}

// 从前往后遍历链表找到元素数据等于目标数据的元素 返回指向这个节点的指针
Position
Find(ElementType X, List L)
{
	Position P;

	P = L->Next;
	while (P != NULL && P->Element != X)
		P = P->Next;

	return P;
}

// 删除元素数据等于目标数据的节点
void
Delete(ElementType X, List L)
{

	Position P, TmpCell;

	P = FindPrevious(X, L);

	if (!IsLast(P, L))
	{
		TmpCell = P->Next;
		P->Next = TmpCell->Next;
		free(TmpCell);
	}

}

// 找到目标节点的前一个节点
Position
FindPrevious(ElementType X, List L)
{
	Position P;

	P = L;
	while (P->Next != NULL && P->Next->Element != X)
		P = P->Next;
	return  P;
}

// 在指定位置的后面插入新元素
void 
Insert(ElementType X, List L, Position P)
{
	Position TmpCell;

	TmpCell = malloc(sizeof(struct Node));
	if (TmpCell == NULL)
		FatalError("Out of space!!!");

	TmpCell->Element = X;
	TmpCell->Next = P->Next;
	P->Next = TmpCell;
}

// 删除整个链表 逐一释放每个链表节点
void
DeleteList(List L)
{
	Position P, Tmp;

	P = L->Next;
	L->Next = NULL;
	while (P != NULL)
	{
		Tmp = P->Next;
		free(P);
		P = Tmp;
	}
}

// 返回链表的头部哨兵节点
Position
Header(List L)
{
	return L;
}

// 返回链表的第一个实际节点
Position
First(List L)
{
	return L->Next;
}

// 得到当前节点的下一个节点
Position
Advance(Position P)
{
	return P->Next;
}

// 得到节点P的数据项
ElementType
Retrieve(Position P)
{
	return P->Element;
}

测试代码 testlist.c

#include <stdio.h>
#include "list.h"

void
PrintList(const List L)
{
	Position P = Header(L);

	if (IsEmpty(L))
		printf("Empty List\n");
	else
	{
		do
		{
			P = Advance(P);
			printf("%d ", Retrieve(P));
		} while (!IsLast(P, L));
		printf("\n");
	}
}

main()
{
	List L;
	Position P;
	int i;

	L = MakeEmpty(NULL);
	P = Header(L);
	PrintList(L);

	for (i = 0; i < 10; i++)
	{
		Insert(i, L, P);
		PrintList(L);
		P = Advance(P);
	}
	for (i = 0; i < 10; i += 2)
		Delete(i, L);

	for (i = 0; i < 10; i++)
    	// i为偶数的时候找不到 条件判断为 1 == 0 i为奇数的时候 找得到 条件判断为 0 == 1 所以代码逻辑没问题的情况下 下面的条件语句不会输出打印信息
		if ((i % 2 == 0) == (Find(i, L) != NULL))
			printf("Find fails\n");

	printf("Finished deletetions\n");

	PrintList(L);

	DeleteList(L);

	return 0;
}

测试输出如下:

Empty List
0
0 1
0 1 2
0 1 2 3
0 1 2 3 4
0 1 2 3 4 5
0 1 2 3 4 5 6
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7 8 9
Finished deletetions
1 3 5 7 9

链表实战

我们通过leetcode上面跟链表有关的1个算法题来练习下如何使用链表

  1. 第23题 给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

 

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]]

输出:[1,1,2,3,4,4,5,6]

解释:链表数组如下: [ 1->4->5, 1->3->4, 2->6 ]

将它们合并到一个有序链表中得到。 1->1->2->3->4->4->5->6

示例 2:

输入:lists = []

输出:[]

示例 3:

输入:lists = [[]]

输出:[]

题解如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

// Node 链表节点
typedef struct ListNode Node;
// 指向Node节点的指针
typedef Node *Position;

// 合并2个有序的链表
 Position
 _merge_two_list(Node *Left, Node *Right)
 {
    if (!Left || !Right)
        return Left ? Left : Right; 
    
    // Header: 一个链表节点 它的next指向我们合并后的链表首节点
    Node Header, *PreHeader = &Header;
    // 用2个指针去遍历2个链表 谁被连接到新链表 谁就向前走一步
    Position CurLeft = Left;
    Position CurRight = Right;
    while(CurLeft && CurRight) 
    {
        if (CurLeft->val <= CurRight->val) 
        {
            PreHeader->next = CurLeft;
            CurLeft = CurLeft->next;
        }
        else
        {
            PreHeader->next = CurRight;
            CurRight = CurRight->next;
        }
        // 新链表移动一步
        PreHeader = PreHeader->next;
    }
    // 如果较短的链表刚好最后一轮被接入新链表 导致此时新链表尾部节点next为null 需要拼起来
    PreHeader->next = CurLeft ? CurLeft : CurRight;
    return Header.next;
 }

Position
merge(struct ListNode **lists, unsigned int start, unsigned int end)
{
    // end不是有效索引 所以start == end 返回空指针
    if (start == end) 
        return NULL;

    unsigned int mid = (start + end) / 2;

    if (start == mid) 
        return lists[start];

    // 归并排序
    Position Left = merge(lists, start, mid);
    Position Right = merge(lists, mid, end);
    Position Res = _merge_two_list(Left, Right);

    return Res;
}

struct ListNode* mergeKLists(struct ListNode** lists, int listsSize){
    return merge(lists, 0, listsSize);
}

代码贴到leetcode答题区即可

参考资料:

  1. 《数据结构与算法分析:C语言描述》