C和指针---ADT:链表、堆栈、队列和树

本文介绍了C语言中如何实现抽象数据类型(ADT)的链表、堆栈、队列和二叉搜索树。讨论了内存分配策略,包括静态数组、动态数组和链式结构的优缺点。堆栈的实现包括动态数组和链式结构,队列的实现则涉及静态数组和链表。文章还详细讲解了二叉搜索树的插入、删除和查找操作,并提到了层次遍历。

1.内存分配

ADT如何获取内存来存储值?三种方案:静态数组、动态分配的数组和动态分配的链式结构。
静态数组要求结构的长度固定,而且长度必须在编译时确定,方案简单,最不容易出错
动态数组,可在运行中决定数组长度,
链式结构提供最大程度的灵活性,每个元素在需要时才单独进行分配,除了不能超过机器的可用内存之外,这种方式对元素的数量几乎没有什么限制,但链式结构的链接字段需要消耗一定的内存。

2.堆栈

堆栈(stack)最鲜明的特点是其后进先出的方式
2.1 堆栈接口
基本的堆栈操作通常被称为push和pop。push是把一个新值压入到堆栈顶部,pop是把堆栈顶部的值移出堆栈并返回这个值。
2.2 实现堆栈
堆栈是最容易实现的ADT,基本方法:当值被push到堆栈时把它们存储于数组中连续的位置上,记住最近一个被push的值的下标。如果需要执行pop操作,只需要简单的减少这个下标的值即可。
/一个堆栈模块的接口/
#define STACK_TYPE int /堆栈存储的值的类型/

/push:把一个新值压入到堆栈中,它的参数是需要被压入/
void push(STACK_TYPE value);

/pop:从堆栈弹出一个值,并将其丢弃/
void pop(void);

/top:返回堆栈顶部元素的值,但不对堆栈进行操作/
STACK_TYPE top(void);

/is_empty 如果堆栈为空,返回ture,否则返回FASLE/
int is_empty(void);

/is_full 如果堆栈为满,返回ture,否则返回FASLE/
int is_full(void);
2.2.1 动态数组堆栈
在静态数组堆栈实现的基础上,增加两个新的函数,创建和销毁堆栈,主要是内存的申请与销毁

2.2.2 链式堆栈

由于只有堆栈的顶部元素才可以访问,所以使用单链表就可以很好的实现链式堆栈,把一个新元素压入到堆栈是通过在链表的起始位置添加一个元素实现。从堆栈中弹出一个元素是通过从链表中移除第1个元素实现的。位于链表头部的元素总是很容易被访问。
/*
**一个用链表实现的堆栈,没有长度限制
*/
#include “stack.h”
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <assert.h>

#define FALSE 0

/*
** 定义一个结构以存储堆栈元素,其中link字段指向堆栈的下一个元素
*/
typedef struct STACK_NODE
{
STACK_TYPE value;
struct STACK_NODE next;
}StackNode;
/

** 指向堆栈中第一个节点的指针
*/
static StackNode *stack;

void create_stack(size_t size)
{
}
void destroy_stack()
{
while (!is_empty())
{
pop();
}
}

void push(STACK_TYPE value)
{
StackNode *new_node;
new_node = malloc(sizeof(StackNode));
assert(new_node != NULL);
new_node->value = value;
new_node->next = stack;
stack = new_node;
}

void pop (void)
{
StackNode *first_node;
assert(!is_empty);
first_node = stack;
stack = first_node->next;
free(first_node);
}

STACK_TYPE top()
{
assert(!is_empty);
return stack->value;
}

int is_empty()
{
return stack == NULL;
}

int is_full()
{
return FALSE;
}

3.队列

队列和堆栈的不同:队列时一种先进先出的结构。排队时一种典型的队列,首先轮到的是排在队伍最前面的人,新入队的人总是排在队伍后面。
3.1 队列接口
/*
**一个队列模块的接口
*/
#include <stdlib.h>
#define QUEUE_TYPE int /队列元素的类型/
/

**创建一个队列,参数指定队列可以存储的元素的最大数量,这个函数只是适用于动态分配数值的队列
/
void create_queue(size_t size);
/

**销毁一个队列,注意这个函数只适用于链式和动态分配数值的队列
/
void destroy_queue();
/

**向队列添加一个新元素,参数就是需要增加的元素
/
void insert(QUEUE_TYPE value);
/

**从队列中移除一个元素并将其丢弃
/
void delete(void);
/

**返回队列中第一个元素的值,但不修改队列本身
/
QUEUE_TYPE first(void);
/

**如果队列为空,返回TRUE,否则返回FALSE
/
int is_empty();
/

**如果队列已满,返回TRUE,否则返回FALSE
/
int is_full();
3.2 实现队列
队列的实现比堆栈要难的多,它需要两个指针:一个指向队头(front),一个指向队尾(rear)。堆栈总是扎根于数据的一端,但当队列的元素插入和删除时,所使用的是数组中的不同元素。
测试队列是否为空的两种方法:
1.引入一个新的变量,用于记录队列中的元素数值,它在每次插入元素时加1,在每次删除元素时减1,对这个变量的值进行测试就可以容易的分清队列空间的空与满
2.重新定义“满”:如果使用数组中的一个元素始终保持不用,这样当队列“满”时,front与rear的值便不同,通过不允许数组完全填满,问题得以避免。
当队列为空时,rear的值必须比front小1。
3.3 数组队列的实现
用一个静态数组实现一个队列,使用“不完全填满数组”的技巧来区分空队列和满队列。
/

**一个静态数组实现的队列,数组的长度只能通过修改#define定义并重新编译模块来调整
/
#include “queue.h”
#include <stdio.h>
#inclued <assert.h>
#define QUEUE_SIZE 100 /队列中元素的最大数值/
#define ARRAY_SIZE (QUEUE_SIZE + 1) /数组的长度/
/

**用存储队列元素的数值和指向队列头和尾的指针
*/
static QUEUE_TYPE queue[ARRAY_SIZE ];
static size_t front = 1;
static size_t rear = 0;

void insert(QUEUE_TYPE value)
{
assertf(!is_full());
rear = (rear + 1) % ARRAY_SIZE;
queue[rear] = value;
}
void delete()
{
assert(!is_empty());
front = (front + 1) % ARRAY_SIZE;
}
QUEUE_TYPE first()
{
assert(!is_empty());
return queue[front];
}
int is_empty()
{
return (rear+1)%ARRAY_SIZE ==front;
}
int is_full()
{
return (rear+2)%ARRAY_SIZE ==front;
}

4.树

树是一种数据结构,要么为空,要么具有一个值并具有零个或多个孩子。二叉树是树的一种特殊形式,每个节点至多有两个孩子,称为左孩子(left)和右孩子(right)。
二叉树常见的特例:二叉搜索树、堆和红黑树。
二叉搜索树具有一个额外的属性:每个节点值比它的左子树的所有节点的值都要大,但比它的右子树的所有节点的值都要小。
堆分为最大堆和最小堆,在最大堆中,根节点的值最大,在最小堆中根节点的值最小。最大值和最小值的问题可以用堆来解决。
红黑树是把书中的节点定义为红、黑两种颜色,并通过规则确保从根节点到叶节点的最长路径的长度不超过最短路径的两倍。
4.1 在二叉搜索树中插入
保持二叉搜索树的属性,其基本算法如下:
如果树为空:
把新值作为根节点插入
否则:
如果新值小于当前节点的值:
把新值插入到当前节点的左子树。
否则:
把新值插入到当前节点的右子树
4.2 从二叉搜索树删除节点
三种情况:删除没有孩子的节点;删除只有一个孩子的节点;删除有两个孩子的节点。
删除没有孩子的节点:删除一个叶节点不会导致任何子树断开,所以不存在重新连接的问题。
删除只有一个孩子的节点:把这个节点的双亲节点与它的孩子连接起来
删除有两个孩子节点:如果一个节点有两个孩子,它的双亲不能连接到它的两个孩子,解决这个问题的一种策略是不删除这个节点,而是删除它的左子树中值最大的那个节点,并用这个值代替原先应被删除的那个节点的值。
4.3 在二叉搜索树中查找
如果树为空:
这个值不存在于树中
否则:
如果这个值和根节点的值相等:
成功找到这个值
否则:
如果这个值小于根节点的值:
查找左子树
否则:
查找右子树
4.4 树的遍历
遍历树的节点有几种不同的次序,最常用的时前序、中序、后序和层次遍历。所有类型的遍历都是从树的根节点或你希望开始遍历的子树的根节点开始。
前序遍历:检查节点的值,然后递归的遍历左子树和右子树。
中序遍历:首先遍历左子树,然后检查当前节点的值,最后遍历右子树。
后序遍历:首先遍历左右子树,然后检查当前节点的值。
层次遍历:首先处理根节点,接着是它的孩子,再接着是它的孙子。

5.链式二叉搜索树

/*
**一个使用动态分配的链式结构实现的二叉搜索树
*/
#include “tree.h”
#include <stdio.h>
#include <malloc.h>

/*
**TreeNode 结构包含了值和两个指向某个树节点的指针
*/
typedef struct TREE_NODE
{
TREE_TYPE value;
struct TREE_NODE *left;
struct TREE_NODE right;
} TreeNode;
/

**指向树根节点的指针
*/
static TreeNode *tree;

void insert(TREE_TYPE value)
{
TreeNode *current;
TreeNode **link;

/*
**从根节点开始
*/
link = &tree;
	/*
**持续查找值,进入合适的子树
*/
while ((current = *link) != NULL)
{
/*
**根据情况,进入左子树或右子树(确认没有出现重复的值)
*/
	if (value < current->value)
	{
		link = &current->left;
	}
	else
	{
		assert(value != current->value);
		link = &current->right;
	}
}

}
/*
**分配一个新节点,适当节点的link字段指向它
*/
current = malloc(sizeof(TreeNode));
assert(current != NULL);
current->value = value;
current->left = NULL;
current->right = NULL;
*link = current;

TREE_TYPE *find (TREE_TYPE value)
{
TreeNode current;
/

**从根节点开始,直到找到这个值,进入合适的子树
*/
current = tree;
while (current != NULL && current->value != value)
{
if (value < current->value)
current = current->left;
else
current = current->right;
}
if (current != NULL)
return &current->value;
else
return NULL;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值