C和指针(学习笔记)_第17章 经典抽象数据类型

1.内存分配

       所有的ADT(抽象数据类型)都必须确定意见事情--如何获取内存来存储值。有三种可选的方案:静态数组、动态分配的数组和动态分配的链式结构。静态数组要求结构的长度固定,而且这个长度必须在编译时确定,但这个方案最为简单且最不容易出错;动态分配需要在增加的复杂性和随之产生的灵活性之间作一番权衡;链式结构提供了最大程度的灵活性,每个元素在需要时才单独进行分配,所以除了不能超过机器的可用内存之外,这种方式对元素的数量几乎没有什么限制,但是链式结构的链接字段需要消耗一定的内存,在链式结构中访问一个特定元素的效率不如数组。

2.堆栈

       堆栈最鲜明的特点就是后进先出(Last-In-First-Out,LIFO)的方式。

       1)堆栈接口:push将新值压入到堆栈的顶部;pop把堆栈顶部的值移出堆栈并返回这个值;top返回顶部元素的值,但它并不把顶部元素从堆栈中移出。

       2)实现堆栈

//一个堆栈模块的接口
#define STACK_TYPE int  //堆栈所存储的值的类型

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

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

//top返回堆栈顶部元素的值,但不对堆栈进行修改
STACK_TYPE top(void)

int is_empty(void)  //如果堆栈为空,返回TRUE,否则返回FALSE

int is_full(void)  //如果堆栈已满,返回TRUE,否则返回FALSE

         a.数组堆栈

#include "stack.h"
#include<assert.h>

#define STACK_SIZE 100

static STACK_TYPE stack[STACK_SIZE];
static int top_element=-1;

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

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

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

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

int is_full(void){
    return top_elememt==STACK_SZIE-1;
}

       b.动态数组堆栈

#include "stack.h"
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<assert.h>

static STACK_TYPE *stack;
static size_t stack_size;
static int top_element=-1;

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

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

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

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

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

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

int is_full(void){
    assert(stack_size>0);
    return top_elememt==stack_size-1;
}

      c.链式堆栈

#include "stack.h"
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<assert.h>

#define FALSE 0

typedef struct STACK_NODE{
    STACK_TYPE value;
    struct STACK_NODE *next;
}StackNode;

static StackNode *stack;

void creat_stack(size_t size){
}

void destroy_stack(void){
    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;
}

void pop(void){
    StackNode *first_node;

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

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

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

int is_full(void){
    return FALSE;
}
 

3.队列

        队列与堆栈不同,队列是一种先进先出(First-IN First-OUT,FIFO)的结构。

       1)队列接口

//一个队列模块的接口
#include<stdlib.h>
#define QUEUE_TYPE int  //队列所存储的值的类型

void create_queue(size_t size);

void destroy_queue(void);

void insert(QUEUE_TYPE value);

void delete(void);

QUEUE_TYPE first(void);

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

int is_full(void)  //如果队列已满,返回TRUE,否则返回FALSE

       2)实现队列

       队列的实现比堆栈要难得多,它需要两个指针——一个指向队头,一个指向队尾。同时数组并不像适合堆栈那样适合队列实现,这是由于队列使用内存的方式决定的。

      a.数组队列

#include"queue.h"
#include<stdio.h>
#include<assert.h>

#define QUEUE_TYPE 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){
    assert(!is_full());
    rear=(rear+1)%ARRAY_SIZE;
    queue[rear]=value;
}

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

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

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

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

4.树

         本文主要阐述二叉搜索树:每个节点的值比它的左子树的所有结点的值都要大,比它的右子树的所有结点的值都要小。

        1)在二叉搜索树中插入

如果树为空:
    把值作为根结点插入
否则:
    如果新值小于当前节点的值
        把新值插入到当前节点的左子树
    否则:
        把新值插入到当前节点的右子树

          2)在二叉搜索树删除结点

          树的删除将导致它的子树和树的其余部分断开——必须重新连接起来,否则会丢失,因此树的删除比队列或堆栈更困难。必须处理的三种情况:a)删除没有孩子的节点;b)删除只有一个孩子的节点;c)删除有两个孩子的节点。

         3)在二叉搜索树中查找

如果树为空:
    这个值不存在于树中
否则:
    如果这个值和根节点的值相等:
        成功找到这个值
    否则:
        如果这个值小于根节点的值:
            查找左子树
        否则:
            查找右子树

        4)树的遍历

        树的遍历:前序(pre-order)、中序(in-order)、后续(post-order)和层次遍历(breadth-first)。前序遍历检查节点的值,然后递归地遍历左子树和右子树;中序遍历首先遍历左子树,然后检查当前节点的值,最后遍历右子树;后序遍历首先遍历左右子树,然后检查当前节点的值;层次遍历逐层检查树的节点,首先处理根节点,接着是它的孩子,再接着是它的孙子,依次类推。

                                       

       前序遍历结果为:20,12,5,9,16,17,25,28,26,29

      中序遍历结果为:5,9,12,16,17,20,25,26,28,29

      后序遍历结果为:9,5,17,16,12,26,29,28,25,20

      层次遍历结果为:20,12,25,5,16,28,9,17,26,29

      虽然前三种遍历方法可以很容易地使用递归来实现,但最后这种层次遍历要采用一种使用队列的迭代算法。

      5)二叉搜索树接口

#define TREE_TYPE int //树的值类型

void insert(TERR_TYPE value);

TREE_TYPE *find(TREE_TYPE value);

void pre_order_traverse(void(*callback)(TREE_TYPE value));

     6)实现二叉搜索树

     a.数组形式的二叉搜索树

      结点N的双亲节点是节点(N+1)/2-1,结点N的左孩子节点是节点2N+1,节点N的右孩子节点是节点2N+2。

//一个使用静态数组实现的二叉搜索树,数组的长度只能通过修改#define定义

#include"tree.h"
#include<assert.h>
#include<stdio.h>

#define TREE_SIZE 100
#define ARRAY_SIZE (TREE_SIZE+1)

//用于存储树的所有节点的数组
static TREE_TYPE tree[ARRAY_SIZE];

static int left_child(int current){
    return current*2;
}

static int right_child(int current){
    return current*2+1
}

void insert(TREE_TYPE value){
    int current;
    assert(value!=0);
    current=1;//从根节点开始
    while(tree[current]!=0){
        if(value<tree[current])
            current=left_child(current);
        else{
            assert(value!=tee[current]);
            current=right_child(current);
        }
        assert(current<ARRAY_SIZE);
    }
    tree[current]=value;
}

TREE_TYPE *find(TREE_TYPE value){
    int current;
    current=1;
    while(current<ARRAY_SIZE && tree[current]!=value){
        if(value<tree[current])
            current=left_child(current);
        else
            current=right_child(current);
    }
    if(current<ARRAY_SIZE)
        return tree+current;
    else
        return 0;
}

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);
    }
}

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

       b.链式二叉搜索树

//一个使用动态分配的链式结构实现二叉搜索树

#include"tree.h"
#include<assert.h>
#include<stdio.h>
#include<malloc.h>

typedef struct TREE_NODE{
    TREE_TYPE value;
    struct TREE_NODE *left;
    strunt 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){
        if(value<current->value)
            current=current->left;
        else
            current=current->right;
    }
    if(current!=NULL)
        return &current->value;
    else
        return NULL;
}

static void do_pre_order_traverse(int 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);
    }
}

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

5.实现的改进

        拥有超过一个的堆栈,拥有超过一种的类型,名字冲突,标准函数库的ADT。

6.总结

       为ADT分配内存有三种技巧:静态数组、动态分配的数组和动态分配的链式结构;堆栈是一种后进先出的结构;队列是一种先进先出的结构;二叉搜索树(BST)是一种数据结构,它或者为空,或者具有一个值并拥有零个、一个或两个孩子(左右孩子),它的孩子本身也是一棵BST。

7.警告的总结

     1)使用断言检查内存是否分配成功是危险的;

     2)数组形式的二叉树节点位置计算公式假定数组的下标从1开始;

     3)把数据封装于对它进行操纵的模块可以防止用户不正确地访问数据;

     4)与类型无关的函数没有类型检查,所以应该小心,确保传递正确类型的数据;

8.编程提示总结

     1)避免使用具有副作用的函数可以使程序更容易理解;

     2)一个模块的接口应该避免暴露它的实现细节;

     3)将数据类型参数化,使它更容易修改;

     4)只有模块对外公布的接口才应该是公用的;

     5)使用断言来防止非法操作;

     6)几个不同的实现使用同一个通用接口使模块具有更强的可互换性;

     7)复用现存的代码而不是对它进行改写;

     8)迭代比尾部递归效率更高;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值