C和指针:第十七章

1. 内存分配

三种内存分配方式:

    1.) 静态数组,它要求结构长度固定,长度必须在编译时确定,这个方案最为简单且最不容易出错;

    2.) 动态分配的数组,在运行时才决定数组的长度,可以分配一个新的,更大的数组,把原来数组的元素复制到新数组中,再删除原来的数组,从而达到改变数组长度的目的。决定是否采用动态数组时,需要由此增加的复杂性和随之产生的灵活性(不需要一个固定的,预先确定的长度)之间作一番权衡。

    3.) 动态分配的链式结构,它提供了最大程序的灵活性,因为每个元素在需要时才单独进行分配,所以除了不能超过机器的可用内存之外,这种方式对元素的数量几乎没有限制。但链式结构的链接字段需要消耗一定的内存,在链式结构中访问一个特定元素的效率不如数组。

 

2. 堆栈(stack)

    堆栈的数据结构特点是后进先出(Last-In First-Out,LIFO)。

    1.) 堆栈接口

    基本的堆栈操作通常被称为push和pop,push就是把一个新值压入到堆栈的顶部,pop就是把堆栈顶部的值移出堆栈并返回这个值。堆栈只提供对它的顶部值进行访问。我们还需要两个函数来使用堆栈,一个是检查堆栈是否为空,另一个是检查堆栈是否已满。

    另一种堆栈接口提供三种基本操作:push, pop 和 top,push 操作和上面一样;pop只负责把顶部元素从堆栈中移除,不负责返回这个值;top返回顶部元素的值,但不把顶部元素从堆栈中移除。

 

    2.) 实现堆栈

    堆栈的基本方法是:被push 到堆栈时把它们存储于数组中连续的位置上,记住最后一个被push值的下标。如果需要执行pop,只要简单地减少这个下标值即可。

    a. 使用静态数组实现堆栈操作,代码如下:

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

#define STACK_SIZE 100  // 设置堆栈大小


// 存储堆栈中值的数组和一个指向堆栈顶部元素的指针
static STACK_SIZE  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 ;
}

 

    b. 使用动态数组实现堆栈操作,代码如下:

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;

// create_stack
void creat_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;  // 将堆栈大小重新设置为0
    free(stack);
    stack = NULL;    // 将指针重新设置为0
}

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

 

    c. 使用动态链式实现堆栈操作,代码如下:

c#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;

// creat_stack
void creat_stack(size_t size)
{
    // 是个空函数,因为链式堆栈不会填满,所以is_full始终返回假
}

// 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. 队列

    队列的数据结构特点是先进先出(First-IN First-OUT, FIFO)。

    1.) 队列接口

    队列的修改是依先进先出的原则进行的,新加的元素总是加入队尾,销毁的元素总是队列头上的。这也正好准确地描述了现实生活中排除的实际体验。一个队列接口可以如下:

 

d#include <stdlib.h>
#define QUEUE_TYPE int

// create_queue 创建一个队列,参数指定队列可以存储元素的最大数量。
void create_queue (size_t size);

// destroy_queue 销毁一个队列
void destroy_queue (void);

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

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

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

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

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

    2.) 实现队列

    实现队列的方法可以是动态数组、链表和循环队列(circular array),现在主要介绍循环队列的实现。它需要两个指针,一个指向队列中第一个元素(front),一个指向队列最后一元素(rear)。示例图如下:

image                    image 

 image    image

注:上面队列为满的队列,实际上没有满,目的是为了让front和rear不指向同一位置,因为队列为空时,它们是指向同一个位置的。这样便可以区分队列为空或为满了。

当队列为空时,front 与 rear关系为:(rear + 1) % QUEUE_SIZE == front

当队列为满时,front 与 rear关系为:(rear + 2) % QUEUE_SIZE == front

代码如下:

d// 用一个静态数级实现的队列
#include "queue.h"
#include <stdlib.h>
#include <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;

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

以上参考:http://student.zjzk.cn/course_ware/data_structure/web/zhanhuoduilie/zhanhuoduilie3.2.2.1.htm

 

4. 二叉树

    树是一种重要的非线性数据结构,树形结构是结点之间有分支,并具有层次关系的结构。二叉树(binary search tree)是树中一种特殊形式,它的每个节点最多有两个子树,分别为左子树和右子树。最上面的那个节点,称为树根,它没有父节点。没有子节点的节点称为叶节点(leaf node)。

    二叉树有个额外的属性:每个节点的值比它的左子树的所有节点的值都要大,但比它的右子树的所有节点的值都要小。

 

    1.) 二叉树接口

    #define TREE_TYPE  int  // 树值的类型

    // insert 向树添加一个新值,参数是需要被添加的值,它必须是原先不存在于树中

    void insert(TREE_TYPE value);

 

    // find 查找一个特定的值,这个值作为第1参数传递给函数

    TREE_TYPE *find(TREE_TYPE value);

 

    // pro_order_traverse 执行树的前序遍历,它的参数是一个回调函数指针,它所指向的函数将在树中处理每个节点被调用,节点的值作为参数传递给这个参数

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

 

    2.) 实现二叉树

   

d#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];

// left_child 计算一个节点左子树的下标
static int left_child (int current)
{
    return current * 2;
}

// right_child 计算一个节点右子树的下标
static int right_child (int current)
{
    return current * 2 + 1;
}

// insert 
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 != tree[current]);
            current = right_child(current);
        }
        assert(current < ARRAY_SIZE);
    }
    
    tree[current] = value;
}

// find
TREE_TYPE *find(TREE_TYPE value)
{
    int current;
    
    // 从根节点开始,直到找到那个值,进入合适的子树
    currtne = 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;
}

// do_pre_order_traverse 执行一层前序遍历,这个函数用于保存当前正在处理的节点信息,并不是用户接口的一部分
static void do_pre_order_traverse(int current, void (*callback) (TREE_TYPE vaule))
{
    if (current < ARRAY_SIZE && tree[current] != 0)
    {
        callback(tree[current]);
        do_pre_order_traverse(left_child(current));
        do_pre_order_traverse(right_child(current));
    }
}

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

转载于:https://www.cnblogs.com/jeff_nie/archive/2010/12/25/1916893.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值