以纯C实现几个基础的数据结构(1)

积跬步而至千里

C语言虽然已经渐渐被现代派的新兴程序员所遗忘(或者说是无视),但是其简单的结构、简单的流程、简单的语法,依旧让C成为了初学者最好的实现。

而数据结构必然是每一个程序员的必修课。List、Queue、Map……都是程序员们每天都要面对并且使用的东西。但是知道数据结构实现以及原理的程序员少之又少(或者说一知半解或是根本没有去针对学习以及自己实现过)。

故人早已整理归纳了大量的书籍知识供我们参考,但是书到用时方恨少。趁着重新学习C语言的机会,我打算按照我自己的理解,将一些基本的数据结构,用纯C实现。正所谓不积硅步无以至千里。而这整理编写C语言实现基本数据结构的过程,便是我的硅步了。

第一部分 是链表和队列。
第二部分 是顺序表和堆栈。
第三部分 是双向链表和双向队列。
第四部分 是平衡二叉树
第五部分 是讨论红黑树
第六部分 是讨论图

注:以下代码均在Windows环境下测试通过,当然,我会尽量做到平台无关


链表

1、我们该怎么做

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。

由上可知,链表是一种由指针将一个个数据节点连接起来的数据结构。相较于顺序结构的大小一定,链表可以动态地创建节点,也就是增加数据地量。

所以我们如此设计:

  1. 一个数据节点
    1.1 一个数据空间
    1.2 一个指向下一个数据节点的指针
  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 初始化空的链表
    1.2 以某个值初始化链表(这步虽然没什么用?,到时候试试能不能初始化一个数组)
  2. 添加数据
    2.1 从头部插入
    2.2 从尾部插入
    2.3 从指定位置插入
  3. 删除数据
    3.1 从头部删除
    3.2 从尾部删除
    3.3 根据位置删除数据
    3.4 根据数据删除数据
  4. 获取数据
    4.1 根据位置获取元素
    4.2 根据查找元素值查找位置
  5. 排序(链表能够排序吗?我会尝试一个默认的从小到大的排序)
  6. 清空
    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()没有实现,可能在将来的某一天补上。

下一篇,是实现一个单向的队列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值