数据结构之链表
链表的简介
数据结构是编程中对数据处理的一种常见的容器,它们的发明总是从简单到复杂的一个过程,在C语言的编程中我们首先接触了基本数据类型,包括定点数(char、short、int、long)、浮点数(float、double)、指针类型。接着了解到了数组(相同数据类型的集合),结构体(不同数据类型的集合)。再往后随着数据规模的增大以及数据之间的复杂关系,又开发出了链表、栈、队列、树(二叉树、字典树、B树等)、图等数结构,使我们对数据的处理能力进一步加强,所以掌握好数据结构非常的重要,本次博客将会着重介绍相关数据结构的实现。首先让我们从最简单的链表开始数据结构之旅。
链表与数组
首先让我们来介绍下链表与数组之间的有缺点,关于数组我们有以下几种定义方式:
int array1[] = {12, 23, 34, 45, 56, 67, 78}; //方法1
int array2[100] = {0}; //方法2
int *array3 = NULL;
array3 = (int *)malloc(sizeof(int) * 100); //方法3
if(array3 != NULL){
fprintf(stderr, "the memory is full!\n");
exit(1);
}
上述是我们定义数组的三种方式,他们对内存的占用位置是不同的,简单的叙述下:
array2和array3是我们的整型数组,它们存在于进程的栈上(后面会对内存的分布进行详细的介绍),而array3则和其他两个略有不同,array3被定义为一个int类型的指针,也就是说array3存储的是一个地址,我们在malloc函数调用中其实是在堆内存中分配了(4bytes * 100 = 400bytes)大小的空间,并且把该内存块的第一个字节的地址存在了array3中,这样相当于array3虽然在栈上,但是它所指向的空间是在堆上存放的。
不管上述的数组是如何定义的,数组都会满足以下几个特点:
1.数组在物理上还是逻辑上都是连续存储的,其中的元素都是可以通过首地址加偏移量进行定位的。
2.使用数组对元素进行查找,时间复杂度非常低,在O(1)的时间复杂度可以完成查找。
3.使用数组进行插入和删除的时候就不那么乐观了,如果被删除或插入的元素在数组的末位,O(1)的时间复杂度也可以完成。但是绝大多数情况下插入和删除的元素都是在数组的中间进行,这样的话时间复杂度将会上升到O(n)。
4.最关键的一点,一旦数组定义完成后,其大小是不可以更改的。这样就要求使用者能够精确的估计数据规模,但现实环境中很可能会造成数组过大或过小而引发浪费和不足的窘境。
由此我们可以发现数组有着很多的不足,还好我们发明了更加灵活的数据结构,链表应该是其中的一个代表(尽管它也不是那么的完美),链表由相同结构的链表节点构成,关于它们是如何能够链接在一起的,我们需要去关注链表节点的结构:
我们可以看到链表节点主要分为两大部分:数据域和链域。数据域是我们最要存储的内容,而链域则代表着一种指针,这个指针指向类型为链表节点,这样每一个节点就可以记录下下一个节点的位置,从而把原本毫不相干的链表节点串接起来。
关于链表节点的结构体定义如下所示:
#define TRUE (1)
#define FALSE (0)
typedef unsigned char Boolean;
typedef struct List_node
{
int data; //数据域
struct List_node *next; //指针域
}List_node;
typedef List_node *List_head;
为了方便链表操作的实现我们重定义了Boolean类型作为逻辑上的真或假。
链表的操作也是在.h文件中进行声明的,我们叫这样的声明为接口,如下是单链表的接口声明:
List_head init_list(void) ; //链表的初始化
void destroy_list(List_head *head) ; //链表的删除
Boolean push_front(List_head head, int value); //头部插入
Boolean push_back(List_head head, int value) ; //尾部插入
Boolean pop_front(List_head head) ; //头部删除
Boolean pop_back(List_head head) ; //尾部删除
Boolean delete_list_node(List_head head,
List_node *node) ; //删除某个指针的值
Boolean find_value_node(List_head head,
int value, List_node **node); //找到指定值的地址
void show_list(List_head head) ; //显示链表信息
void sort_list_ascend(List_head head) ; //链表的升序排序
void sort_list_descend(List_head head) ; //链表的降序排序
int list_length(List_head head) ; //链表的长度
设计完链表的接口,我们就需要全力以赴的实现这些接口,就好像我们在设计完产品的配料表和使用说明书之后就需要专心致志的设计具体的产品,所以关于链表接口的具体实现我们放在list.c中实现,如下所示:
List_head init_list(void) //链表的初始化
{
List_head head = NULL;
head = buy_node();
return head;
}
void destroy_list(List_head *head) //链表的删除
{
List_node *p = NULL;
List_node *q = NULL;
// List_node **
if(head == NULL || *head == NULL){
return ;
}
p = *head;
while(p != NULL){
q = p;
p = p->next;
free(q);
}
*head = NULL;
}
Boolean push_front(List_head head, int value) //头部插入
{
List_node *node = NULL;
if(head == NULL){
return FALSE;
}
//购买节点并进行赋值
node = buy_node();
node->data = value;
// printf("adfadf");
//头部插入节点
// NULL->next;
node->next = head->next;
head->next = node;
return TRUE;
}
Boolean push_back(List_head head, int value) //尾部插入
{
List_node *node = NULL;
List_node *p_node = NULL;
if(head == NULL){
return FALSE;
}
node = buy_node();
node->data = value;
p_node = head;
//1.找到末尾
while(p_node->next != NULL){
p_node = p_node->next;
}
//2.插入到末尾
p_node->next = node;
return TRUE;
}
Boolean pop_front(List_head head) //头部删除
{
List_node *p_node = NULL;
if(head == NULL || head->next == NULL){
//链表为空或者没有有效节点,头部删除失败
return FALSE;
}
p_node = head->next;
head->next = head->next->next;
free(p_node);
return TRUE;
}
Boolean pop_back(List_head head) //尾部删除
{
List_node *p_node = NULL;
if(head == NULL || head->next == NULL){
return FALSE;
}
p_node = head;
//找到最后一个节点并且删除
while(p_node->next->next != NULL){
p_node = p_node->next;
}
free(p_node->next);
p_node->next = NULL;
return TRUE;
}
//talk
Boolean delete_list_node(List_head head,
List_node *node) //删除某个指针的值
{
List_node *p_node = NULL;
if(head == NULL || head->next == NULL || node == NULL){
return FALSE;
}
p_node = head;
while(p_node->next != node){
p_node = p_node->next;
if(p_node == NULL){ //已经遍历完整个链表也没有找到指定的节点
break ;
}
}
if(p_node == NULL){ //没找到,返回删除失败
return FALSE;
}
//找到并且删除
p_node->next = node->next;
free(node);
return TRUE;
}
Boolean find_value_node(List_head head,
int value, List_node **node) //找到指定值的地址
{
List_node *p_node = NULL;
if(head == NULL || head->next == NULL || node == NULL){
return FALSE;
}
p_node = head->next;
while(p_node != NULL){
if(p_node->data == value){
*node = p_node;
return TRUE;
}
p_node = p_node->next;
}
return FALSE;
}
void show_list(List_head head) //显示链表信息
{
List_node *p_node = NULL;
if(head == NULL){
return ;
}
for(p_node = head->next; p_node != NULL; p_node = p_node->next){
printf("%d ", p_node->data);
}
printf("\n");
}
/*
* int i = 0;
* int j = 0;
*
* for(i = 0; i < length - 1; ++i){
* for(j = i + 1; j < length; ++j){
* if(array[i] > array[j]){
* swap(&array[i], &array[j], sizeof(array[i]));
* }
* }
* }
*
* */
#define POINTER_SIZE (sizeof(void *))
#define data_size(p_node) (((char *)((p_node) + 1) - (POINTER_SIZE)) - ((char *)(p_node)))
void sort_list_ascend(List_head head) //链表的升序排序
{
List_node *p_node = NULL;
List_node *q_node = NULL;
//如果链表为空,没有元素或者只有一个元素,不进行排序操作
if(head == NULL || head->next == NULL
|| head->next->next == NULL){
return ;
}
for(p_node = head->next; p_node != NULL; p_node = p_node->next){
for(q_node = p_node->next; q_node != NULL; q_node = q_node->next){
if(p_node->data > q_node->data){
//只交换数据域
swap(p_node, q_node, data_size(p_node));
}
}
}
}
void sort_list_descend(List_head head) ; //链表的降序排序
int list_length(List_head head) //链表的长度
{
List_node *p_node = NULL;
int length = 0;
if(head == NULL){
return length;
}
p_node = head->next;
while(p_node != NULL){
length++;
p_node = p_node->next;
}
return length;
}
static void swap(void *a, void *b, int length) //交换函数
{
void *temp = Malloc(length);
memcpy(temp, a, length);
memcpy(a, b, length);
memcpy(b, temp, length);
free(temp);
}
static void *Malloc(size_t size) //内存申请
{
void *result = malloc(size);
if(result == NULL){
fprintf(stderr, "the memory is full!\n");
exit(1);
}
return result;
}
static List_node *buy_node(void) //生成一个链表节点
{
List_node *result = NULL;
result = (List_node *)Malloc(sizeof(List_node));
bzero(result, sizeof(List_node));
// int data;
// struct List_node *next;
return result;
}
设计完链表的操作后我们需要对这个功能进行测试,测试代码如下所示:
int main(int argc, char **argv)
{
List_head head = NULL;
int i = 0;
head = init_list(); //链表的初始化
for(i = 0; i < 10; ++i){
push_front(head, rand() % 100);
}
// push_back(head, 10);
// push_back(head, 20);
// push_back(head, 30);
// push_back(head, 40);
// push_back(head, 50);
// push_back(head, 60);
show_list(head);
pop_front(head);
pop_back(head);
show_list(head);
// List_node *find = NULL;
// Boolean ok = find_value_node(head, 30, &find);
// if(ok == TRUE){
// printf("the %d value found\n", find->data);
// }
sort_list_ascend(head);
show_list(head);
destroy_list(&head); //链表的销毁
return 0;
}
上述就是我们关于链表的基本操作,虽然非常简单(确实它的功能十分单一,操作不是那么的完善,而且与数据域的类型息息相关),但是清晰的描述出一个链表应该具有的操作。在后续的章节中我们将会介绍更加实用的链表操作,敬请期待。