数据结构与算法:链表基础
2016-08-20 10:21
1476 查看
链表是通过指针连接数据元素的一种数据结构。链表由一系列结点(NODE)组成,每个结点包含两个域:存储数据元素的数据域(data field)和存储下一个结点地址的指针域(pointer field)。链表是除了数组以外使用得最多的数据结构,学习链表需要掌握以下几个重要的概念:
头指针(head pointer)—指向链表中第一个结点的指针。
首元结点—指链表中存储第一个数据元素的结点。
头结点(head node)—在首元结点之前附设的一个结点,该结点不存储数据元素,其指针域指向首元结点,其作用主要是为了方便对链表的操作。它可以对空表、非空表以及首元结点的操作进行统一处理。头指针指向头结点,单链表中可以没有头结点,此时头指针指向首元结点。
链表(下面默认指单链表)可以看成由一系列结点组成,每个结点又指向下一个结点。
其中head为头指针,存储数据项Data Items的是数据域,存储直接后继(即下一个结点)位置的是指针域。需注意以下几点:
链表必须包含头指针
每个节点都包含数据域与指针域
结点之间通过指针相连
最后一个结点的指针为空
单链表——也称线性链表,每个结点只包含一个指针域,指针的指向是单向的。
双向链表——双向链表包含两个指针域,一个指向直接后继(immediate successor),一个指向直接前驱(immediate predecessor)。
循环链表——表中最后一个结点的指针域指向头结点,整个链表形成一个环。
链表的基本操作也包括:
插入
删除
搜索
遍历
逆置
在链表中插入一个新结点需要不止一步(但比数组简单),我们先通过图示来学习,暂时不涉及具体代码。首先,我们需要创建一个与链表结点有相同结构的结点,然后,确定插入位置。
假如我们要把新结点B(NewNode)插入到结点A(左边结点LeftNode)、C(右边结点RightNode)之间,那么需要让B指向C。
图示如下:
然后将结点A指向新结点。
这样新结点就插入到两结点之间了,最终结果如下:
在链表开头插入结点步骤于此类似,之后的代码中会涉及到,在链表末尾插入结点时,最后一个结点指向新结点,新结点指向NULL。
删除操作也需要不止一步,首先,使用搜索算法定位要删除的结点。
目标结点(TargetNode)的前驱(LeftNode)应当指向目标结点的后继。
然后断开目标结点与其后继的连接。
如果被删除的结点无需再次使用的话我们就可以释放掉它占用的内存。最终结果如下:
转置操作相对复杂一些,我们需要让头指针指向最后一个结点,并且逆置整个链表。
首先,我们来操作最后一个结点,其指针本应指向NULL,现在我们让它指向其前驱结点。
为了确保当前结点不会丢失,必须设置一个临时结点来保存它,并使当前结点的后继指向该临时结点,从头开始依次进行操作。
除了头指针指向的第一个节点外,所有节点都应指向其直接前驱并使其直接前驱成为新的直接后继。第一个结点指向NULL。
然后通过临时结点,使头指针指向新的首元节点。
输出结果如下:
头指针(head pointer)—指向链表中第一个结点的指针。
首元结点—指链表中存储第一个数据元素的结点。
头结点(head node)—在首元结点之前附设的一个结点,该结点不存储数据元素,其指针域指向首元结点,其作用主要是为了方便对链表的操作。它可以对空表、非空表以及首元结点的操作进行统一处理。头指针指向头结点,单链表中可以没有头结点,此时头指针指向首元结点。
链表的表示
链表(下面默认指单链表)可以看成由一系列结点组成,每个结点又指向下一个结点。其中head为头指针,存储数据项Data Items的是数据域,存储直接后继(即下一个结点)位置的是指针域。需注意以下几点:
链表必须包含头指针
每个节点都包含数据域与指针域
结点之间通过指针相连
最后一个结点的指针为空
链表的类型
单链表——也称线性链表,每个结点只包含一个指针域,指针的指向是单向的。双向链表——双向链表包含两个指针域,一个指向直接后继(immediate successor),一个指向直接前驱(immediate predecessor)。
循环链表——表中最后一个结点的指针域指向头结点,整个链表形成一个环。
基本操作
链表的基本操作也包括:插入
删除
搜索
遍历
逆置
插入操作
在链表中插入一个新结点需要不止一步(但比数组简单),我们先通过图示来学习,暂时不涉及具体代码。首先,我们需要创建一个与链表结点有相同结构的结点,然后,确定插入位置。假如我们要把新结点B(NewNode)插入到结点A(左边结点LeftNode)、C(右边结点RightNode)之间,那么需要让B指向C。
NewNode.next −> RightNode;
图示如下:
然后将结点A指向新结点。
LeftNode.next −> NewNode;
这样新结点就插入到两结点之间了,最终结果如下:
在链表开头插入结点步骤于此类似,之后的代码中会涉及到,在链表末尾插入结点时,最后一个结点指向新结点,新结点指向NULL。
删除操作
删除操作也需要不止一步,首先,使用搜索算法定位要删除的结点。目标结点(TargetNode)的前驱(LeftNode)应当指向目标结点的后继。
LeftNode.next −> TargetNode.next;
然后断开目标结点与其后继的连接。
TargetNode.next −> NULL;
如果被删除的结点无需再次使用的话我们就可以释放掉它占用的内存。最终结果如下:
逆置操作
转置操作相对复杂一些,我们需要让头指针指向最后一个结点,并且逆置整个链表。首先,我们来操作最后一个结点,其指针本应指向NULL,现在我们让它指向其前驱结点。
为了确保当前结点不会丢失,必须设置一个临时结点来保存它,并使当前结点的后继指向该临时结点,从头开始依次进行操作。
除了头指针指向的第一个节点外,所有节点都应指向其直接前驱并使其直接前驱成为新的直接后继。第一个结点指向NULL。
然后通过临时结点,使头指针指向新的首元节点。
C语言实现
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdbool.h> struct node { int data; int key; struct node *next; }; struct node *head = NULL; struct node *current = NULL; //display the list void printList() { struct node *ptr = head; printf("\n[ "); //start from the beginning while(ptr != NULL) { printf("(%d,%d) ",ptr->key,ptr->data); ptr = ptr->next; } printf(" ]"); } //insert link at the first location void insertFirst(int key, int data) { //create a link struct node *link = (struct node*) malloc(sizeof(struct node)); link->key = key; link->data = data; //point it to old first node link->next = head; //point first to new first node head = link; } //delete first item struct node* deleteFirst() { //save reference to first link struct node *tempLink = head; //mark next to first link as first head = head->next; //return the deleted link return tempLink; } //is list empty bool isEmpty() { return head == NULL; } int length() { int length = 0; struct node *current; for(current = head; current != NULL; current = current->next) { length++; } return length; } //find a link with given key struct node* find(int key){ //start from the first link struct node* current = head; //if list is empty if(head == NULL) { return NULL; } //navigate through list while(current->key != key){ //if it is last node if(current->next == NULL){ return NULL; }else { //go to next link current = current->next; } } //if data found, return the current Link return current; } //delete a link with given key struct node* delete(int key){ //start from the first link struct node* current = head; struct node* previous = NULL; //if list is empty if(head == NULL){ return NULL; } //navigate through list while(current->key != key){ //if it is last node if(current->next == NULL){ return NULL; }else { //store reference to current link previous = current; //move to next link current = current->next; } } //found a match, update the link if(current == head) { //change first to point to next link head = head->next; }else { //bypass the current link previous->next = current->next; } return current; } void sort(){ int i, j, k, tempKey, tempData ; struct node *current; struct node *next; int size = length(); k = size ; for ( i = 0 ; i < size - 1 ; i++, k-- ) { current = head ; next = head->next ; for ( j = 1 ; j < k ; j++ ) { if ( current->data > next->data ) { tempData = current->data ; current->data = next->data; next->data = tempData ; tempKey = current->key; current->key = next->key; next->key = tempKey; } current = current->next; next = next->next; } } } void reverse(struct node** head_ref) { struct node* prev = NULL; struct node* current = *head_ref; struct node* next; while (current != NULL) { next = current->next; current->next = prev; prev = current; current = next; } *head_ref = prev; } int main() { insertFirst(1,10); insertFirst(2,20); insertFirst(3,30); insertFirst(4,1); insertFirst(5,40); insertFirst(6,56); printf("Original List: "); //print list printList(); while(!isEmpty()){ struct node *temp = deleteFirst(); printf("\nDeleted value:"); printf("(%d,%d) ",temp->key,temp->data); } printf("\nList after deleting all items: "); printList(); insertFirst(1,10); insertFirst(2,20); insertFirst(3,30); insertFirst(4,1); insertFirst(5,40); insertFirst(6,56); printf("\nRestored List: "); printList(); printf("\n"); struct node *foundLink = find(4); if(foundLink != NULL){ printf("Element found: "); printf("(%d,%d) ",foundLink->key,foundLink->data); printf("\n"); }else { printf("Element not found."); } delete(4); printf("List after deleting an item: "); printList(); printf("\n"); foundLink = find(4); if(foundLink != NULL){ printf("Element found: "); printf("(%d,%d) ",foundLink->key,foundLink->data); printf("\n"); }else { printf("Element not found."); } printf("\n"); sort(); printf("List after sorting the data: "); printList(); reverse(&head); printf("\nList after reversing the data: "); printList(); return 0; }
输出结果如下:
Original List: [ (6,56) (5,40) (4,1) (3,30) (2,20) (1,10) ] Deleted value:(6,56) Deleted value:(5,40) Deleted value:(4,1) Deleted value:(3,30) Deleted value:(2,20) Deleted value:(1,10) List after deleting all items: [ ] Restored List: [ (6,56) (5,40) (4,1) (3,30) (2,20) (1,10) ] Element found: (4,1) List after deleting an item: [ (6,56) (5,40) (3,30) (2,20) (1,10) ] Element not found. List after sorting the data: [ (1,10) (2,20) (3,30) (5,40) (6,56) ] List after reversing the data: [ (6,56) (5,40) (3,30) (2,20) (1,10) ]
相关文章推荐
- 算法与数据结构基础系列(一): 链表的常见问题分析及实现
- 一步一步复习数据结构和算法基础-链表(2)
- 数据结构 链表基础算法
- 一步一步复习数据结构和算法基础-链表(3)
- 一步一步复习数据结构和算法基础-链表(1)
- 一步一步复习数据结构和算法基础-循环链表
- 新手学习数据结构与算法---链表归并
- 数据结构和算法基础
- 【数据结构与算法基础】单链表及其应用基数排序 / Singly Linked List and radix sort
- 《Delphi 算法与数据结构》学习与感悟[10]: 双向链表
- 一步一步复习数据结构和算法基础-线性表
- 新手学习数据结构与算法---单链表的基本操作
- 新手学习数据结构与算法---链表多项式
- 《Delphi 算法与数据结构》学习与感悟[7]: 链表与数组的异同
- 数据结构基本算法:单向链表
- 数据结构学习笔记 --- 线性表 (一些常见的关于链表的算法和面试题)
- 《Delphi 算法与数据结构》学习与感悟[9]: 循环链表
- 《Delphi 算法与数据结构》学习与感悟[8]: 单向链表的添加、删除与遍历
- 算法导论之链表基础知识回顾
- C#数据结构和算法学习系列四----基础排序算法