Linked List 链表详解

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

linked list basics

linked list problems

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


本文代码下载:http://download.csdn.net/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单步调试,跟踪指针变化




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值