经典抽象数据类型

《C与指针》读书笔记
1. 内存分配
所有的ADT都必须确定一件事情——如何获取内存来存储值。有三种可选的方案:静态数组、动态数组、动态链式结构。
2 堆栈
堆栈这种数据最鲜明的特点就是其后进先出(Last-In First-Out)的方式。
2.1 堆栈接口
堆栈基本的操作通常被称为push和pop。push就是把一个新值压入到堆栈的顶部。pop就是把堆栈顶部的值移出堆栈并返回这个值。访问顶部元素的值的接口:top。
2.2 实现堆栈
堆栈头文件部分
/*
** Interface for a stack module
*/

#define STACK_TYPE int /* Type of value on the stack */

/*
** push
** Pushes a new value on the stack. The argument is the value
** to be pushed.
*/
void push( STACK_TYPE value );

/*
** pop
** Pops a value off of the stack, discarding it.
*/
void pop( void );

/*
** top
** Returns the topmost value on the stack without changing the
** stack.
*/
STACK_TYPE top( void );

/*
** is_empty
** Returns TRUE if the stack is empty, else FALSE.
*/
int is_empty( void );

/*
** is_full
** Returns TRUE if the stack is full, else FALSE.
*/
int is_full( void );

(1)数组堆栈
/*
** A stack implemented with a static array. The array size can
** be adjusted only by changing the #define and recompiling
** the module.
*/
#include "stack.h"
#include <assert.h>

#define STACK_SIZE 100 /* Max # of values on the stack */

/*
** The array that holds the values on the stack, and a pointer
** to the topmost value on the stack.
*/
static STACK_TYPE stack[ STACK_SIZE ];
static int top_element = -1;

/*
** push
*/
void
push( STACK_TYPE value )
{
assert( !is_full() );
top_element += 1;
stack[ top_element ] = value;
}

/*
** pop
*/
void
pop( void )
{
assert( !is_empty() );
top_element -= 1;
}

/*
** top
*/
STACK_TYPE top( void )
{
assert( !is_empty() );
return stack[ top_element ];
}

/*
** is_empty
*/
int
is_empty( void )
{
return top_element == -1;
}

/*
** is_full
*/
int
is_full( void )
{
return top_element == STACK_SIZE - 1;
}

(2)动态数组堆栈
/*
** A stack implemented with a dynamically allocated array.
** The array size is given when create is called, which must
** happen before any other stack operations are attempted.
*/
#include "stack.h"
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <assert.h>

/*
** The array that holds the values on the stack, and a pointer
** to the topmost value on the stack.
*/
static STACK_TYPE *stack;
static size_t stack_size;
static int top_element = -1;

/*
** create_stack
*/
void
create_stack( size_t size )
{
assert( stack_size == 0 );
stack_size = size;
stack = malloc( stack_size * sizeof( STACK_TYPE ) );
assert( stack != NULL );
}

/*
** destroy_stack
*/
void
destroy_stack( void )
{
assert( stack_size > 0 );
stack_size = 0;
free( stack );
stack = NULL;
}

/*
** push
*/
void
push( STACK_TYPE value )
{
assert( !is_full() );
top_element += 1;
stack[ top_element ] = value;
}

/*
** pop
*/
void
pop( void )
{
assert( !is_empty() );
top_element -= 1;
}

/*
** top
*/
STACK_TYPE top( void )
{
assert( !is_empty() );
return stack[ top_element ];
}

/*
** is_empty
*/
int
is_empty( void )
{
assert( stack_size > 0 );
return top_element == -1;
}

/*
** is_full
*/
int
is_full( void )
{
assert( stack_size > 0 );
return top_element == stack_size - 1;
}
(3)链式堆栈
/*
** A stack implemented with a linked list. This stack has no size
** limit.
*/
#include "stack.h"
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <assert.h>

#define FALSE 0

/*
** Define a structure to hold one value. The link field will
** point to the next value on the stack.
*/
typedef struct STACK_NODE {
STACK_TYPE value;
struct STACK_NODE *next;
} StackNode;

/*
** A pointer to the topmost node on the stack.
*/
static StackNode *stack;

/*
** create_stack
*/
void
create_stack( size_t size )
{
}

/*
** destroy_stack
*/
void
destroy_stack( void )
{
while( !is_empty() )
pop();
}

/*
** push
*/
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;
}

/*
** pop
*/
void
pop( void )
{
StackNode *first_node;

assert( !is_empty() );
first_node = stack;
stack = first_node->next;
free( first_node );
}

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

/*
** is_empty
*/
int
is_empty( void )
{
return stack == NULL;
}

/*
** is_full
*/
int
is_full( void )
{
return FALSE;
}
3. 队列
队列与堆栈不同:队列是一种先进先出的结构。
3.1 队列接口
/*
** Interface for a queue module
*/

#include <stdlib.h>

#define QUEUE_TYPE int /* Type of value in the queue */

/*
** create_queue
** Creates a queue. The argument indicates the maximum number
** of values that the queue will hold. NOTE: this applies only
** to the dynamically allocated array implementation.
*/
void create_queue( size_t size );

/*
** destroy_queue
** Destroys a queue. NOTE: this applies only to the linked and
** dynamically allocated array implementations.
*/
void destroy_queue( void );

/*
** insert
** Adds a new value on the queue. The argument is the value
** to be inserted.
*/
void insert( QUEUE_TYPE value );

/*
** delete
** Removes a value from the queue, discarding it.
*/
void delete( void );

/*
** first
** Returns the first value on the queue without changing the
** queue itself.
*/
QUEUE_TYPE first( void );

/*
** is_empty
** Returns TRUE if the queue is empty, else FALSE
*/
int is_empty( void );

/*
** is_full
** Returns TRUE if the queue is full, else FALSE
*/
int is_full( void );
3.2 实现队列
队列需要两个指针——一个指向队头,一个指向队尾。
队列使用“循环数组”进行实现。
上图插入一个元素之后的结果如下:
循环数组很容易实现——当尾部下标移出尾部时,把它设置为0.用下面的代码实现。
rear += 1;
if(rear >= QUEUE_SIZE)
rear = 0;
下面的方法具有相同的结果。
rear = (rear + 1)%QUEUE_SIZE;
在对front增值时也必须使用同一技巧。
但是,判断循环数组是否为空或者已满更为困难。因为队列空与队列满的时候,front与rear的值都是一样。所以无法判断。
有两种方法可以解决这个问题。第一种是引入一个新变量,用于记录队列中的元素数量。
第二种方法是重新定义“满”的含义。如果使数组中的一个元素始终保留不用,这样当队列“满”时front和rear的值便不相同。
但是,当队列为空时,front和rear的值应该是什么?当队列只有一个元素时,我们需要使front和rear都指向这个元素。为了使rear在第一次插入后指向插入的元素,当队列为空时rear的值必须比front小1。另外,从队列中删除最后一个元素后的状态也是如此。
所以当满足下面条件时,队列为空:
(rear + 1) %QUEUE_SIZE == front
由于在font和rear正好满足这个关系之前,必须停止插入元素,所以满足下列条件时,队列必须认为已“满”。
(rear + 2)&QUEUE_SIZE == front
数组队列的实现:
/*
** A queue implemented with a static array. The array size can
** be adjusted only by changing the #define and recompiling
** the module.
*/
#include "queue.h"
#include <stdio.h>
#include <assert.h>

#define QUEUE_SIZE 100 /* Max # of values on the queue */
#define ARRAY_SIZE ( QUEUE_SIZE + 1 ) /* Size of array */

/*
** The array that holds the values on the queue, and pointers
** to the front and rear of the queue.
*/
static QUEUE_TYPE queue[ ARRAY_SIZE ];
static size_t front = 1;
static size_t rear = 0;

/*
** insert
*/
void
insert( QUEUE_TYPE value )
{
assert( !is_full() );
rear = ( rear + 1 ) % ARRAY_SIZE;
queue[ rear ] = value;
}

/*
** delete
*/
void
delete( void )
{
assert( !is_empty() );
front = ( front + 1 ) % ARRAY_SIZE;
}

/*
** first
*/
QUEUE_TYPE first( void )
{
assert( !is_empty() );
return queue[ front ];
}

/*
** is_empty
*/
int
is_empty( void )
{
return ( rear + 1 ) % ARRAY_SIZE == front;
}

/*
** is_full
*/
int
is_full( void )
{
return ( rear + 2 ) % ARRAY_SIZE == front;
}

4. 树
树是一种数据结构,它要么空,要么具有一个值并具有零个或多个孩子。此处只研究二叉树。
二叉搜索树具有一个属性:每个节点的值比它的左子树节点的值都要大,但比它右子树的所有节点的值都要小。
4.1 在二叉搜索树中插入
基本算法如下:
如果输为空:
把新值作为根节点插入。
否则:
如果新值小于当前节点的值:
把新值插入到当前节点的左子树
否则:
把新值插入到当前节点的右子树
4.2 从二叉搜索树删除节点
我们必须处理三种情况:
(1)删除没有孩子的节点
删除一个叶节点不会导致任何子树断开,直接删除。
(2)删除只有一个孩子的节点
把这个节点的双亲节点和它的孩子链接起来就可以了。
(3)删除有两个孩子的节点
解决这个问题的一种策略是不删除这个节点,而是删除它的左子树中最大的那个节点,并用这个值代替原先应被删除的那个节点的值。
4.3 在二叉搜索树中查找
下面是基本算法:
如果树为空:
这个值不存在与树中
否则:
如果这个值和根节点的值相等:
成功找到这个值
否则:
如果这个值小于根节点的值:
查找左子树
否则:
查找右子树
4.4 树的遍历
前序:根-左-右
中序:左-根-右
后序:左-右-根
层次遍历:先处理根节点,接着是它的孩子,再接着是它的孙子,依次类推。
4.5 二叉搜索树接口
/*
** Interface for a binary search tree module
*/

#define TREE_TYPE int /* Type of value in the tree */

/*
** insert
** Add a new value to the tree. The argument is the value
** to be added and must not already exist in the tree.
*/
void insert( TREE_TYPE value );

/*
** find
** Searches for a specific value, which is passed as the first
** argument.
*/
TREE_TYPE *find( TREE_TYPE value );

/*
** pre_order_traverse
** Does a pre-order traversal of the tree. The argument is a
** pointer to a callback function that will be called for
** each node in the tree, with the value passed as an argument.
*/
void pre_order_traverse( void (*callback)( TREE_TYPE value ) );

4.6 实现二叉搜索树
(1)数组形式的二叉搜索树
用数组表示树的关键是使用下标来寻找某个特定值的双亲和孩子。规则如下:
节点N的双亲是节点N/2.
节点N的左孩子是节点2N.
节点N的右孩子是节点2N + 1.
下面是实现程序。根节点从第二个元素开始,即从下标[1]开始。
/*
** A binary search tree implemented with a static array. The
** array size can be adjusted only by changing the #define and
** recompiling the module.
*/
#include "tree.h"
#include <assert.h>
#include <stdio.h>

#define TREE_SIZE 100 /* Max # of values in the tree */
#define ARRAY_SIZE ( TREE_SIZE + 1 )

/*
** The array that holds the values in the tree.
*/
static TREE_TYPE tree[ ARRAY_SIZE ];

/*
** left_child
** Compute the subscript of the left child of a node.
*/
static int
left_child( int current )
{
return current * 2;
}

/*
** right_child
** Compute the subscript of the right child of a node.
*/
static int
right_child( int current )
{
return current * 2 + 1;
}

/*
** insert
*/
void
insert( TREE_TYPE value )
{
int current;

/*
** Ensure the value is nonzero, because zero indicates an
** unused node.
*/
assert( value != 0 );

/*
** Start with the root node.
*/
current = 1;

/*
** Go to the proper subtree until we reach a leaf.
*/
while( tree[ current ] != 0 ){
/*
** Go to the left or right subtree, as appropriate.
** (And make sure we don't have a duplicate value!)
*/
if( value < tree[ current ] )
current = left_child( current );
else {
assert( value != tree[ current ] );
current = right_child( current );
}
assert( current < ARRAY_SIZE );
}
tree[ current ] = value;
}

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

/*
** Start with the root node. Until we find the value,
** go to the proper subtree.
*/
current = 1;

while( current < ARRAY_SIZE && tree[ current ] != value ){
/*
** Go to the left or right subtree, as appropriate.
*/
if( value < tree[ current ] )
current = left_child( current );
else
current = right_child( current );
}
if( current < ARRAY_SIZE )
return tree + current;
else
return 0;
}

/*
** do_pre_order_traverse
** Do one level of a pre-order traverse. This helper function
** is needed to save the information of which node we're
** currently processing; this is not a part of the client's
** interface.
*/
static void
do_pre_order_traverse( int current,
void (*callback)( TREE_TYPE value ) )
{
if( current < ARRAY_SIZE && tree[ current ] != 0 ){
callback( tree[ current ] );
do_pre_order_traverse( left_child( current ),
callback );
do_pre_order_traverse( right_child( current ),
callback );
}
}

/*
** pre_order_traverse
*/
void
pre_order_traverse( void (*callback)( TREE_TYPE value ) )
{
do_pre_order_traverse( 1, callback );
}

(2)链式二叉搜索树

/*
** A binary search tree implemented by linking dynamically allocated
** structures.
*/
#include "tree.h"
#include <assert.h>
#include <stdio.h>
#include <malloc.h>

/*
** The TreeNode structure holds the value and pointers for one
** tree node.
*/
typedef struct TREE_NODE {
TREE_TYPE value;
struct TREE_NODE *left;
struct TREE_NODE *right;
} TreeNode;

/*
** The pointer to the root node in the tree.
*/
static TreeNode *tree;

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

/*
** Start with the root node.
*/
link = &tree;

/*
** As long as we keep finding values, go to the proper
** subtree.
*/
while( (current = *link) != NULL ){
/*
** Go to the left or right subtree, as appropriate.
** (And make sure we don't have a duplicate value!)
*/
if( value < current->value )
link = &current->left;
else {
assert( value != current->value );
link = &current->right;
}
}
/*
** Allocate a new node; make the proper link field point
** to it.
*/
current = malloc( sizeof( TreeNode ) );
assert( current != NULL );
current->value = value;
current->left = NULL;
current->right = NULL;
*link = current;
}

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

/*
** Start with the root node. Until we find the value,
** go to the proper subtree.
*/
current = tree;

while( current != NULL && current->value != value ){
/*
** Go to the left or right subtree, as appropriate.
*/
if( value < current->value )
current = current->left;
else
current = current->right;
}
if( current != NULL )
return &current->value;
else
return NULL;
}

/*
** do_pre_order_traverse
** Do one level of a pre-order traverse. This helper function
** is needed to save the information of which node we're
** currently processing; this is not a part of the
** client's interface.
*/
static void
do_pre_order_traverse( TreeNode *current,
void (*callback)( TREE_TYPE value ) )
{
if( current != NULL ){
callback( current->value );
do_pre_order_traverse( current->left, callback );
do_pre_order_traverse( current->right, callback );
}
}

/*
** pre_order_traverse
*/
void
pre_order_traverse( void (*callback)( TREE_TYPE value ) )
{
do_pre_order_traverse( tree, callback );
}
5 实现的改进
使用宏定义通用的数据结构,下面是通用堆栈的实现和使用:
(1)实现
/*
** GENERIC implementation of a stack with a static array. The array
** size is given as one of the arguments when the stack is
** instantiated.
*/
#include <assert.h>

#define GENERIC_STACK( STACK_TYPE, SUFFIX, STACK_SIZE ) \
\
static STACK_TYPE stack##SUFFIX[ STACK_SIZE ]; \
static int top_element##SUFFIX = -1; \
\
int \
is_empty##SUFFIX( void ) \
{ \
return top_element##SUFFIX == -1; \
} \
\
int \
is_full##SUFFIX( void ) \
{ \
return top_element##SUFFIX == STACK_SIZE - 1; \
} \
\
void \
push##SUFFIX( STACK_TYPE value ) \
{ \
assert( !is_full##SUFFIX() ); \
top_element##SUFFIX += 1; \
stack##SUFFIX[ top_element##SUFFIX ] = value; \
} \
\
void \
pop##SUFFIX( void ) \
{ \
assert( !is_empty##SUFFIX() ); \
top_element##SUFFIX -= 1; \
} \
\
STACK_TYPE top##SUFFIX( void ) \
{ \
assert( !is_empty##SUFFIX() ); \
return stack##SUFFIX[ top_element##SUFFIX ]; \
}
(2)使用
/*
** A client that uses the generic stack module to create two stacks
** holding different types of data.
*/
#include <stdlib.h>
#include <stdio.h>
#include "g_stack.h"

/*
** Create two stacks, one of integers and one of floats.
*/
GENERIC_STACK( int, _int, 10 )
GENERIC_STACK( float, _float, 5 )

int
main()
{
/*
** Push several values on each stack.
*/
push_int( 5 );
push_int( 22 );
push_int( 15 );
push_float( 25.3 );
push_float( -40.5 );

/*
** Empty the integer stack and print the values.
*/
while( !is_empty_int() ){
printf( "Popping %d\n", top_int() );
pop_int();
}

/*
** Empty the float stack and print the values.
*/
while( !is_empty_float() ){
printf( "Popping %f\n", top_float() );
pop_float();
}

return EXIT_SUCCESS;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值