本文所有代码采用C语言实现。
参考文献: 《数据结构(C语言版)》 严蔚敏 吴伟民 编著
开发平台:Ubuntu 11.04
编译器:gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4)
线性结构:在数据元素的非空有限集中,有且仅有一个开始结点(没有直接前趋)和一个终止结点(没有直接后继),除开始结点和终止结点之外,所有其他结点有且仅有一个直接前趋结点和一个直接后继结点。
线性表是一种典型的线性结构。
链式存储结构:逻辑上相邻的两个数据元素其存储的内存地址不要求紧邻。
1、单链表的定义
链式存储的线性表就叫做链表。
由于链表并不是使用一段连续的内存来依次存储其所有结点的,所以每个结点都必须使用一个指针域(用来指向其直接后继结点)来构建数据元素之间的逻辑关系。
而每个结点只包含一个指针域的链表就叫做单链表。
单链表的存取必须从头指针开始进行,头指针指向开始结点的存储地址,同时,终止结点的指针域必须为NULL,如下图所示:
有时候,我们会在单链表的开始结点之前附加一个头结点,头结点的指针域指向开始结点,它的数据域可以不存储任何信息,也可以存储一些其他的附加信息。
2、单链表的运算
例子公用代码:
#include <stdio.h> #include <string.h> #include <stdlib.h>
#define NAMELEN 20 typedef struct student { char name[NAMELEN]; unsigned long num; char sex; //M = male, F = female unsigned short age; unsigned short marks; }data_type; typedef struct list_node { data_type data; struct list_node *next; }list_node_t, *list_node_p; static void init_data(list_node_t *tmp) { char *line = NULL; size_t len = 0; printf("Please enter the student's information from name, num, " "sex, age to marks.\ne.g.: Richard Tang 201201034 F 19 93\n"); if (getline(&line, &len, stdin) < 0) perror("getline"); /* %[a-z,A-Z, ], a nonstandard extension of GNU libc */ if (sscanf(line, "%[a-z,A-Z, ] %lu %c %hu %hu", tmp->data.name, &tmp->data.num, &tmp->data.sex, &tmp->data.age, &tmp->data.marks) != 5) printf("invalid data, the number of input items " "is not equal to five.\n"); free(line); }
(1)、创建
2.1.1 头插法创建单链表
每一个刚创建的新结点都插入到当前链表的头指针后,如下图所示:
例子代码可用来创建学生基本信息的单链表,当输入quit后则停止继续创建。
static list_node_t *create_list_at_head(void) { char cond[5]; list_node_p tmp, head = NULL; printf("Press Enter to begin to create a linked list."); while (strncmp(fgets(cond, 5, stdin), "quit", 4)) { /* Remove the extra characters for fgets() */ stdin->_IO_read_ptr = stdin->_IO_read_end; tmp = (list_node_t *)malloc(sizeof(list_node_t)); if (!tmp) perror("allocate dynamic memory"); init_data(tmp); tmp -> next = head; head = tmp; printf("Have created a node.\nPress Enter to continue " "(or type \"quit\" to quit): "); } return head; }
2.1.2 尾插法创建单链表
从头指针开始,每个新创建的结点都插入到当前链表的表尾,如下图所示:
参考代码如下:
static list_node_t *create_list_at_tail(void) { char cond[5]; list_node_p head = NULL; list_node_t *tmp, *work = NULL; printf("Press Enter to begin to create a linked list."); while (strncmp(fgets(cond, 5, stdin), "quit", 4)) { /* Remove the extra characters for fgets() */ stdin->_IO_read_ptr = stdin->_IO_read_end; tmp = (list_node_t *)malloc(sizeof(list_node_t)); if (!tmp) perror("allocate dynamic memory"); init_data(tmp); tmp -> next = NULL; if (head == NULL) head = tmp; else work -> next = tmp; work = tmp; printf("Have created a node.\nPress Enter to continue " "(or type \"quit\" to quit): "); } return head; }
2.1.3 使用尾插法创建带有头结点的单链表
使用头结点使得对开始结点和其他结点的操作并无二致,因此代码也就更直观和简单了,如下图所示:
参考代码如下:
static list_node_t *create_list_at_tail_with_headnode(void) { char cond[5]; list_node_p head = (list_node_t *)malloc(sizeof(list_node_t)); head -> next = NULL; list_node_t *tmp, *work = head; printf("Press Enter to begin to create a linked list."); while (strncmp(fgets(cond, 5, stdin), "quit", 4)) { /* Remove the extra characters for fgets() */ stdin->_IO_read_ptr = stdin->_IO_read_end; tmp = (list_node_t *)malloc(sizeof(list_node_t)); if (!tmp) perror("allocate dynamic memory"); init_data(tmp); tmp -> next = NULL; work -> next = tmp; work = tmp; printf("Have created a node.\nPress Enter to continue " "(or type \"quit\" to quit): "); } return head; }
(2)、查找
2.2.1 按序号查找
单链表的按序号查找只能从链表的头指针出发,顺着指针域逐个结点地往下搜索,直到找到想要的结点为止。
参考代码如下:
static list_node_t *get_node_with_headnode(list_node_p head, int i) { list_node_p p = head; int j = 0; while (p -> next && j < i) { p = p -> next; j++; } if (i == j) return p; else return NULL; }
2.2.2 按值查找
例子中以学生的学号为键值进行查找,这样做的好处是能保证键值的唯一性(可在创建链表的代码中增加对学号输入重复的纠错能力)。
参考代码如下:
static list_node_t *locate_node_with_headnode(list_node_p head, unsigned long key) { list_node_p p = head -> next; while (p && p->data.num != key) p = p -> next; return p; }