积跬步而至千里
C语言虽然已经渐渐被现代派的新兴程序员所遗忘(或者说是无视),但是其简单的结构、简单的流程、简单的语法,依旧让C成为了初学者最好的实现。
而数据结构必然是每一个程序员的必修课。List、Queue、Map……都是程序员们每天都要面对并且使用的东西。但是知道数据结构实现以及原理的程序员少之又少(或者说一知半解或是根本没有去针对学习以及自己实现过)。
故人早已整理归纳了大量的书籍知识供我们参考,但是书到用时方恨少。趁着重新学习C语言的机会,我打算按照我自己的理解,将一些基本的数据结构,用纯C实现。正所谓不积硅步无以至千里。而这整理编写C语言实现基本数据结构的过程,便是我的硅步了。
第一部分 是链表和队列。
第二部分 是顺序表和堆栈。
第三部分 是双向链表和双向队列。
第四部分 是平衡二叉树
第五部分 是讨论红黑树
第六部分 是讨论图
注:以下代码均在Windows环境下测试通过,当然,我会尽量做到平台无关
链表
1、我们该怎么做
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。
由上可知,链表是一种由指针将一个个数据节点连接起来的数据结构。相较于顺序结构的大小一定,链表可以动态地创建节点,也就是增加数据地量。
所以我们如此设计:
- 一个数据节点
1.1 一个数据空间
1.2 一个指向下一个数据节点的指针 - 为了方便操作,我还打算声明一个链表本身
2.1 有一个头指针指向第一个数据节点
2.2 有一个current指针指向最后的数据节点
2.3 有一个计数器
这么做虽然更加繁琐了,但是通过这种没什么技术含量的方式,可以更加轻松地操作作为一个整体的链表。虽然这样可能就与链表那非连续、位置无关的初衷有所违背了吧。不过我也只是记录了第一个数据和最后一个数据的地址而已,其他无关。
2、写出声明
// 声明一个链表节点
struct LinkedListNode{
int data;
struct LinkedListNode *next;
};
typedef struct LinkedListNode Node;
// 声明一个所谓的链表
struct LinkedList{
Node *head;
Node *current;
int count;
};
typedef struct LinkedList List;
对了,上述节点中的data
目前为了方便设定为整型int。当然没有CPP的泛型,我也无法进行更多的设定了。但是用CPP的话,明明有STL,就不需要重新自己写了吧,即使是为了学习目的。
3、再次考虑
接下来就是对链表的操作了。
此链表仅仅是单向链表,所以没有那么复杂的双向操作。
于是我归类了几种基本操作:
- 初始化
1.1 初始化空的链表
1.2 以某个值初始化链表(这步虽然没什么用?,到时候试试能不能初始化一个数组) - 添加数据
2.1 从头部插入
2.2 从尾部插入
2.3 从指定位置插入 - 删除数据
3.1 从头部删除
3.2 从尾部删除
3.3 根据位置删除数据
3.4 根据数据删除数据 - 获取数据
4.1 根据位置获取元素
4.2 根据查找元素值查找位置 - 排序(链表能够排序吗?我会尝试一个默认的从小到大的排序)
- 清空
6.1 清空链表,释放所有资源
6.2 判断是否为空
4、声明函数
void InitLinkedList(List *list);
void InitLinkedListByParam(List *list, int item);
void AddItemToTail(List *list, int item);
void AddItemToHead(List *list, int item);
void Insert(List *list, int index, int item);
int DeleteFromTail(List *list);
int DeleteFromHead(List *list);
int DeleteItem(List *list, int index);
int Delete(List *list, int item);
int GetItem(const List list, int index);
int IndexOf(const List list, int item);
// 此函数是为了方便测试
void PrintList(const List list);
void Clear(List *list);
int IsEmpty(const List list);
void Sort(List *list);
接下来我就闲阐述一下我的实现思路。
void InitLinkedList(List *list);
这里初始化的不是链表的节点,毕竟链表的节点仅仅只是由一个数据空间一个指针构成,没什么好初始化的。这里初始化的是链表本身。*head
指向NULL,*current
指向NULL,count
赋值为0即可。
void InitLinkedListByParam(List *list, int item);
这个函数是以一个数字为初始化参数,将链表初始化,不过其行为约等于在链表尾部(这里再次重申这个是我定义的链表本身,链表的搜索必须从第一个开始,但是由于我定义了我自己的链表的封装,所以可以直接取到尾部)插入一个值。在这里我们可以先给*current
分配空间,使用(Node *)malloc(sizeof(Node))
即可创建一个节点。然后count
自增,将*current
(*current
此刻其实是指向了创建的节点的指针)的data
赋值,再将其*next
的指针指向NULL。最后将*current
赋值给*head
即可。
void AddItemToTail(List *list, int item);
往链表尾部插入一个数据。和上面的思想一样,先开辟一个空间,创建一个节点,将前一个节点(目前是*current
)的next指针指向现在的这个节点,再将*current
指针指向现在的节点,最后将现在的next指针指向NULL即可。
void AddItemToHead(List *list, int item);
往链表前插入数据,就比较简单了,逻辑上与往后插入是一样的,就是指针的变动而已。
void Insert(List *list, int index, int item);
这个在链表中间插入其实也不复杂。先要判断参数的合法性。然后将指定位置的节点的next指针指向新创建的节点,然后将新的节点的next指针指向原先的后一个节点。
int DeleteFromTail(List *list);
从尾部删除,只需要将尾部节点的前一个节点的next指针指向NULL,然后free最后一个节点。
int DeleteFromHead(List *list);
从头部删除,只需将*head
指针指向下一个节点,然后free原本第一个节点只好。
int DeleteItem(List *list, int index);
从指定位置删除和插入元素是差不多的操作。
int Delete(List *list, int item);
以元素删除元素主要是遍历链表的操作,其实就是按照指针来查找元素。
5、开始动手吧
链表结构体和对应的操作函数已经定义好了,那么就开始实现吧。
#define OK 1
#define FAIL -1
#define FALSE -1
#define TRUE 1
#define NONE 0
void InitLinkedList(List *list){
list->head = NULL;
list->current = NULL;
list->count = 0;
}
void InitLinkedListByParam(List *list, int item){
list->head = NULL;
list->current = (Node *)malloc(sizeof(Node));
list->current->data = item;
list->current->next = NULL;
list->head = list->current;
list->count = 1;
}
void AddItemToTail(List *list, int item){
Node *p = (Node *)malloc(sizeof(Node));
p->data = item;
p->next = NULL;
if (list->head == NULL){
list->head = p;
}else{
list->current->next = p;
}
list->current = p;
list->count++;
}
void AddItemToHead(List *list, int item){
Node *p = (Node *)malloc(sizeof(Node));
p->data = item;
if (list->head == NULL){
list->current = p;
p->next = NULL;
}else{
p->next = list->head;
}
list->head = p;
list->count++;
}
void Insert(List *list, int index, int item){
if (index > list->count - 1 || index < 0){
return;
}
Node *p = (Node *)malloc(sizeof(Node));
p->data = item;
if (list->head == NULL){
p->next = NULL;
list->current = p;
list->head = p;
}else{
if (index == 0){
p->next = list->head;
list->head = p;
list->count++;
return;
}
Node *t = list->head;
for (int i = 0; i < index - 1; i++){
if (t->next != NULL)
t = t->next;
else break;
}
if (t->next != NULL){
Node *next = t->next;
t->next = p;
p->next = next;
}else{
list->current->next = p;
list->current = p;
}
}
list->count++;
}
int DeleteFromTail(List *list){
if (IsEmpty(*list) == TRUE){
return FAIL;
}
Node *p = list->head;
while (p->next != list->current){
p = p->next;
}
free(list->current);
p->next = NULL;
list->current = p;
list->count--;
return OK;
}
int DeleteFromHead(List *list){
if (IsEmpty(*list) == TRUE){
return FAIL;
}
Node *p = list->head->next;
free(list->head);
list->head = p;
list->count--;
return OK;
}
int DeleteItem(List *list, int index){
if (IsEmpty(*list) == TRUE || index > list->count - 1){
return FAIL;
}
Node *p = list->head;
if (index == 0){
list->head = p->next;
free(p);
return OK;
}
Node *f = list->head;
for (int i = 0; i < index; i++){
p = p->next;
}
for (int i = 0; i < index - 1; i++){
f = f->next;
}
f->next = p->next;
p->next = NULL;
free(p);
return OK;
}
int Delete(List *list, int item){
if (IsEmpty(*list) == TRUE){
return FAIL;
}
Node *p = list->head;
while (p->data != item){
if (p->next == NULL){
return FAIL;
}
p = p->next;
}
if (p == list->head){
list->head = p->next;
free(p);
}else{
Node *f = list->head;
while (f->next != p){
f = f->next;
}
if (p->next == NULL){
f->next = NULL;
}else{
f->next = p->next;
}
free(p);
}
list->count--;
return OK;
}
int GetItem(const List list, int index){
if (IsEmpty(list) == TRUE || index > list.count - 1){
return NONE;
}
Node * p = list.head;
for (int i = 0; i < index; i++){
p = p->next;
}
return p->data;
}
int IndexOf(const List list, int item){
if (IsEmpty(list) == TRUE){
return NONE;
}
Node *p = list.head;
int index = 0;
while (p->data != item){
if (p->next == NULL){
return FAIL;
}
p = p->next;
index++;
}
return index;
}
void PrintList(const List list){
if (IsEmpty(list) == TRUE){
printf("This List is Empty.\n");
return;
}
Node *p = list.head;
printf("%d ", p->data);
while (p->next != NULL){
p = p->next;
printf("%d ", p->data);
}
printf("\r\n");
}
int IsEmpty(const List list){
if (list.count == 0 || (list.head == NULL && list.current == NULL)){
return TRUE;
}
return FALSE;
}
6、测试
我已经准备了一些数据以供测试,如下:
List list1,list2;
InitLinkedList(&list1);
printf("list1:");PrintList(list1);
InitLinkedListByParam(&list2, 12);
printf("list2:");PrintList(list2);
AddItemToHead(&list2, 45);
AddItemToTail(&list1, 78);
AddItemToTail(&list2, 178);
printf("list1:"); PrintList(list1);
printf("list2:"); PrintList(list2);
Insert(&list1, 0, 99);
Insert(&list2, 1, 99);
printf("list1:"); PrintList(list1);
printf("list2:"); PrintList(list2);
DeleteFromHead(&list2);
DeleteFromTail(&list1);
printf("list1:"); PrintList(list1);
printf("list2:"); PrintList(list2);
DeleteItem(&list1, 2);
DeleteItem(&list2, 1);
printf("list1:"); PrintList(list1);
printf("list2:"); PrintList(list2);
Delete(&list1, 99);
Delete(&list2, 99);
printf("list1:"); PrintList(list1);
printf("list2:"); PrintList(list2);
for (int i = 1; i < 11; i++){
AddItemToTail(&list1, i);
}
printf("list1:"); PrintList(list1);
printf("%d\n", GetItem(list1, 2));
printf("%d\n", IndexOf(list1, 5));
return 0;
测试结果如下,还是能顺利运行的。
7、阶段小总结
为什么没有从顺序表开始,是因为顺序表约等于数组,或者说就是普通的数组。所以没有什么新意。在第二部分进行堆栈的实现时,可以作为补充进行实现。毕竟大家都是顺序结构嘛。关于代码格式,因为为了节省行数,所以做了相应的妥协,改成了Java式的代码风格。
最后,清空函数,Clear()与排序函数Sort()没有实现,可能在将来的某一天补上。
下一篇,是实现一个单向的队列。