Linked List 链表详解

本文详细介绍链表的基本概念、实现及多种实用操作,包括插入、删除、排序、合并等,并提供了丰富的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文是stanford cs library中两篇关于linked list的文章合体版

linked list basics

linked list problems

我觉得这两篇讲linked list的文章写的非常非常好,所以在博客里自己写一下,也算是温习巩固数据结构的知识了


本文代码下载:http://download.youkuaiyun.com/detail/stevemarbo/4090566


本文结合一个实际的例子来解释什么是linked list,如何使用linked list


不过在解释linked list之前,必须先要了解指针的相关知识,这里先做一个简单回顾一下指针


1.指针存储了对另一个变量的引用。 指针的值是一个地址。


2.在C语言中,   *  这个符号被称为解析符号,它用来取得指针所指向的那个变量的值,举个例子

#include<stdio.h>
void main ()
{
	int a=3;

	int* p = &a;

	printf("*p=%d\n",*p);
}

上面这个例子在printf打印函数中,*p就是把指针p指向的变量a的值取出来

我记得我在学习C语言指针的时候,一直搞不清楚 * 这个符号,一个原因就是 int* p=&a  在这行代码中, int* p是申明了一个指针,这一行代码也能够拆成两行来写

int *p;
p=&a;


但是请注意,int* p和 int *p这两种写法是一样的,都是申明了一个指向整形变量的指针,但是 int* p更好,因为 int* 表示这是一个指向整形的指针,名字叫做p,而 int *p 的写法容易让人误解为指针的名字叫做 *p,我当时学C语言时在这个问题上纠结了很久


3. & 这个符号叫做取地址符,在上面的例子中, int *p = &a; 这行代码就是把变量 a 的地址赋给指针 p


关于指针内容其实非常多,这里只是一个简单的回顾,想看更多关于指针的资料,我推荐大家看这篇文章, pointer and memory, 这篇文章是stanford大学的教授写的,我看完以后,有一种豁然开朗的感觉


linked list是一种和数组有几分相似的数据结构,我相信大家都非常了解数组,数组有3个很明显的缺点:

1. 数组的长度是固定的。比如申明了一个长度为100的整形数组 int a[100], 数组a的长度就是100,如果这时需要存储200个数字,那么a就用不了了

2. 数组造成存储空间的浪费。如果这时数组a只存储了 1个整数,那么它的其他99个位置就相当于浪费了

3. 往数组里插入元素的开销是非常大的。比如要在数组的第一个元素的位置上插入1个整数,那就得把数组其他位置上的元素都向后移动一位


正因为数组有以上的这些缺点,所以产生了linked list,linked list很好的克服了以上的三个缺点


首先,定义一个名字叫做node的结构体

struct node {
	int data;
	struct node* next;
};

这个结构体中包含一个整数,和一个指向结构体node的指针


接下来我们写一个函数,生成一个linked list

struct node* BuildOneTwoThreeFourFive() {
	struct node* head = NULL;
	struct node* second = NULL;
	struct node* third = NULL;
	struct node* forth = NULL;
	struct node* fifth = NULL;

	head = malloc(sizeof(struct node));
	second = malloc(sizeof(struct node));
	third = malloc(sizeof(struct node));
	forth = malloc(sizeof(struct node));
	fifth = malloc(sizeof(struct node));

	head->data = 1;
	head->next = second;

	second->data = 2;
	second->next = third;

	third->data = 3;
	third->next = forth;

	forth->data = 4;
	forth->next = fifth;

	fifth->data = 5;
	fifth->next= NULL;
	return head;
}

现在,这个linked list就会有5个元素,如果打印出这个linked list的data值,那就是 {1,2,3,4,5}


求linked list长度的函数

int Length(struct node* head) {
	struct node* current = head;
	int count = 0;

	while(current != NULL) {
		count++;
		current = current->next;
	}

	return count;
}


往这个linked list里添加一个节点,这个节点要在linked list的头部,

void Push(struct node** headRef, int data) {
	struct node* newNode = malloc(sizeof(struct node));

	newNode->data = data;
	newNode->next = *headRef;
	*headRef = newNode;
}

这个Push函数是一个比较难理解的地方,因为它的参数列表有一个参数 struct node** headRef。

我们都知道, struct node* head表示的是一个指向struct node的指针,名字是head

那么,struct node** headRef表示的就是一个指向struct node的指针的指针,名字是headRef

当然,这个地方为什么用指针的指针,还是很有讲究的,至于为什么,可以参考本文开头时提到的第一篇文章,由于篇幅有限,这里不做解释了


写一个函数,函数有两个参数,一个是linked list,另一个是一个整数,查询在这个linked list里有几个元素的data值和整个整数相等

int Length(struct node* head) {
	struct node* current = head;
	int count = 0;

	while(current != NULL) {
		count++;
		current = current->next;
	}

	return count;
}


取得linked list中的第N个元素的data值

int GetNth(struct node* head, int index) {
	struct node* current = head;
	int count = 0;
	
	while(current != NULL) {
		if(count == index) return (current->data);
		count++;
		current = current->next;
	}

	assert(0);
}

删除这个linked list

void DeleteList(struct node** headRef) {
	struct node* current = *headRef;
	struct node* next;

	while(current != NULL) {

		next = current->next;
		free(current);
		current = next;
	}

	*headRef = NULL;
}


取出这个linked list里头部的那个节点的data值,并且销毁第一个节点

int Pop(struct node** headRef) {
	struct node* head;
	int result;

	head = *headRef;

	assert(head != NULL);

	result = head->data;
	*headRef = head->next;

	free(head);
	return result;
}

在第n个节点的位置上插入一个节点

void InsertNth(struct node** headRef, int index, int data) {
	if(index == 0) Push(headRef,data);

	else {
		struct node* current = *headRef;
		int i;

		for(i=0; i<index-1; i++) {
			assert(current != NULL);
			current = current->next;
		}

		assert(current != NULL);

		Push(&(current->next),data);

	}
}


假设一个linked list已经按照data的升序排列好了,这时往这个linked list添加一个节点,这个节点会被加入的正确的位置上

void SortedInsert(struct node** headRef, struct node* newNode) {	
	if(*headRef == NULL || (*headRef)->data >= newNode->data) {
                newNode->next = *headRef;
		*headRef = newNode;
	}
	else {
		struct node* current = *headRef;
		while(current->next != NULL && current->next->data<newNode->data) {
			current = current->next;
		}
		newNode->next = current->next;
		current->next = newNode;
	}
}


linked list插入排序

void InsertSort(struct node** headRef) {
	struct node* result = NULL;
	struct node* current = *headRef;
	struct node* next;

	while(current != NULL) {
		next = current->next;
		SortedInsert(&result, current);
		current = next;
	}

	*headRef = result;
}


把一个链表加入到另一个链表的尾部

void Append(struct node** aRef, struct node** bRef) {
	struct node* current;
	if(*aRef == NULL) {
		*aRef = *bRef;
	}
	else {
		current = *aRef;
		while(current->next != NULL)
			current = current->next;

		current->next = *bRef;
	}

	*bRef = NULL;
}


把一个链表对半分,比如,链表是{2,3,5,7,11}, 分割后产生两个链表,第一个{2,3,5}, 第二个{7,11}

void FrontBackSplit(struct node* source, struct node** frontRef, struct node** backRef) {
	int len = Length(source);

	int i;
	struct node* current = source;

	if(len<2) {
		*frontRef = source;
		*backRef = NULL;
	}
	else {
		int hopCount  = (len-1)/2;
		for(i=0; i<hopCount; i++)
			current = current->next;

		*frontRef = source;
		*backRef = current->next;
		current->next = NULL;
	}
}

去除链表中的重复节点

void RemoveDuplicates(struct node* head) {
	struct node* current = head;
	if(current == NULL) return;

	// compare current node with next node
	while(current->next != NULL) {
		if(current->data == current->next->data) {
			struct node* nextNext = current->next->next;
			free(current->next);
			current->next = nextNext;
		}
		else {
			current = current->next;
		}
	}
}


把第二个链表的第一个节点添加到第一个链表的头节点上

void MoveNode(struct node** destRef, struct node** sourceRef) {
	struct node* newNode = *sourceRef;
	assert(newNode != NULL);

	*sourceRef = newNode->next;

	newNode->next = *destRef;
	*destRef = newNode;
}


AlternatingSplit

每隔一个节点取出的合成一个linked list,比如,{a,b,c,d,e,f}在 AlternatingSplit()后生成两个链表 {a,c,e}   和 {b,d,f}

void AlternatingSplit(struct node* source, struct node** aRef, struct node** bRef) {
	struct node* a = NULL;
	struct node* b = NULL;

	struct node* current = source;
	while(current != NULL) {
		MoveNode(&a,¤t);
		if(current!=NULL)
			MoveNode(&b,¤t);
	}

	*aRef = a;
	*bRef = b;
}


ShuffleMerge

这个函数的目的需要举例说明,比如,两个链表{1,2,3}和{7,13,1},两个链表shufflemerge后的结构就是{1,7,2,13,3,1}

也就是分别取出每个从每个链表头节点合并成一个链表

struct node* ShuffleMerge(struct node* a, struct node* b) {
	struct node* result;
	struct node* recur;

	if(a == NULL) return b;
	else if(b == NULL) return a;
	else {
		recur = ShuffleMerge(a->next, b->next);

		result = a;
		a->next = b;
		b->next = recur;
		return result;
	}
}


SortedMerge

假设两个链表都是按照升序递增的,把两个链表合并,并且合并后的链表也是按照升序排列的

struct node* SortedMerge(struct node* a, struct node* b) {
	struct node dummy;
	struct node* tail = &dummy;

	dummy.next = NULL;

	while(1) {
		if(a == NULL) {
			tail->next = b;
			break;
		}

		else if (b == NULL) {
			tail->next = a;
			break;
		}
		if(a->data <= b->data) {
			MoveNode(&(tail->next),&a);
		}
		else {
			MoveNode(&(tail->next),&b);
		}
		tail = tail->next;
	}

	return dummy.next;
}

MergeSort

链表的归并排序

void MergeSort(struct node** headRef) {
	struct node* head = *headRef;
	struct node* a;
	struct node* b;

	if((head==NULL) || (head->next)==NULL)
		return;

	FrontBackSplit(head,&a,&b);

	MergeSort(&a);
	MergeSort(&b);

	*headRef = SortedMerge(a,b);
}


SortedIntersect

假设两个链表是按照升序排列的,找出两个链表相等的节点


struct node* SortedIntersect(struct node* a, struct node* b) {
	struct node dummy;
	struct node* tail = &dummy;

	dummy.next = NULL;

	while(a != NULL && b != NULL) {
		if(a->data == b->data) {
			Push((&tail->next),a->data);
			tail = tail->next;
			a = a->next;
			b = b->next;
		}
		else if(a->data < b->data) {
			a = a->next;
		}
		else {
			b = b->next;
		}
	}

	return dummy.next;
}


Reverse

链表的倒置

这个是非递归版本

void Reverse(struct node** headRef) {
	struct node* result = NULL;
	struct node* current = *headRef;
	struct node* next;

	while(current != NULL) {
		next = current->next;

		current->next = result;
		result = current;

		current = next;
	}

	*headRef = result;
}


下面这个是递归版本

void RecursiveReverse(struct node** headRef) {
	struct node* first;
	struct node* rest;

	if(*headRef == NULL) return;

	first = *headRef;		
	rest = first->next;		
		
	if(rest == NULL) return;	

	RecursiveReverse(&rest);

	first->next->next = first;
	first->next = NULL;

	*headRef = rest;
}


倒置的操作比较复杂,需要画图去跟指针的变化

当然,我觉得最好的方法还是用gdb单步调试,跟踪指针变化




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值