本文所有代码采用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;
}