数据结构(暂时完结)

 

时间:2015-4-30 11:30

 

 

加油
———————————————————————————————————————————————————————— 

——数据结构概述

    一、定义
        我们如何把现实中大量而复杂的问题以特定的数据类型(个体)和特定的存储结构(个体之间的关系)保存到主存储器(内存)中,
        以及在此基础上为实现某个功能(比如查找某个元素,删除某个元素,对所有元素进行排序)而执行的相应操作,这个相应的操作也
        叫做算法(算法就是操作)。

        数据结构 = 个体 + 个体的关系
        算法 = 对存储数据的操作(存储方式不同,算法也不同)

    二、算法
        解题的方法和步骤
        衡量算法的标准
            1、时间复杂度
                    程序大概执行的次数,而非执行的时间。
                    执行的时间依赖于硬件,而硬件在不断的发展变化,所以不能把时间当做一个标准。
            2、空间复杂度
                    算法执行过程中大概所占用的最大的内存。
            3、难易程度
                    算法不但要优,而且要容易被理解。
            4、健壮性
                    保证有错误输入时程序不会出错。

    三、数据结构的地位
        数据结构是软件中最核心的课程。
        程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言

——预备知识

        一、指针
            1、指针的重要性:
                指针是C语言的灵魂
            2、定义:
                地址
                    内存单元的编号。
                    从0开始的非负整数。
                    范围:0 ~ FFFFFFFFF(0 ~ 4G-1),针对于32位CPU。
                指针:
                    指针就是地址,地址就是指针。
                    指针变量是存放内存单元地址的变量。
                    指针的本质是一个操作受限的非负整数。
            3、指针和数组
                指针和一维数组
                数组名:
                    一位数组名是一个指针常量。
                    它存放的是一维数组第一个元素的地址。
                    它的值不能被改变。
                    一位数组名指向的是数组的第一个元素
                下标和指针的关系
                    a[i] == *(a+i)(偏移地址)
                    假设指针变量的名字为a,则a+i的值是a+i*(a所指向的变量所占的字节数)
                    也可以写成:i[a]
                指针变量的运算
                    指针变量不能相加,不能相乘,不能相除。
                    如果两个指针变量属于同一数组,则可以相减。
                    指针变量可以加减一个整数,前提是最终结果不能超过指针变量所指向内存的地址范围。
        二、结构体
            1、为什么会出现结构体
                为了表示一些复杂的数据,而普通的基本类型变量无法满足需求。
            2、什么是结构体
                结构体是用户根据实际需求自己定义的复合数据类型。
            3、如何使用结构体
                两种方式:
                    *   struct Student st = {100,"zhangsan",20};
                        st.sid = 20;
                    *   struct Student * pst = &st;
                        pst->sid = 20;//表示pst所指向的结构体变量中的sid成员
            4、注意事项
                结构体变量不能加减乘除,但是可以相互赋值。
                普通结构体变量和结构体指针变量作为函数传参的问题。
                代码如下:
-----------------------------------------------------------------------------------------------------------

# include<stdio.h>
# include<string.h>
 
struct Student
{
    int sid;
    char name[20];
    int age;
};
//修改值
void fun(struct Student * pst)
{
    pst->age = 20;
    strcpy(pst->name, "lisi");
    pst->sid = 100;
}
//输出值
void show(struct Student * pst)
{
    printf("%d %s %d\n", pst->sid, pst->name, pst->age);
}
 
int main(void)
{
    struct Student st;//已经为st分配了内存空间,只不过内存中没有值。例如 int i;i已经有内存空间。
    fun(&st);
    //printf("%d %s %d\n",st.sid,st.name,st.age);
    show(&st);
    return 0;
}

-----------------------------------------------------------------------------------------------------------

        三、动态内存的分配和释放

# include<stdio.h>
# include<stdlib.h>
 
int main(void)
{
    int arr[5] = { 1,2,3,4,5 };
    int len;
    printf("请输入您需要分配的数组的长度:len = ");
    scanf("%d", &len);
    int *pArr = (int *)malloc(sizeof(int) * len);
    *pArr = 1;//类似于a[0] = 1
    pArr[1] = 2;//类似于a[1] = 2
    printf("%d %d\n",*pArr,pArr[1]);
    //可以把pArr当做一个普通数组来使用
    for (int i = 0; i < len; i++)
    {
        scanf("%d", &pArr[i]);
    }
    //输出
    for (int i = 0; i < len; i++)
    {
        printf("%d\t", *(pArr + i));
    }
    free(pArr);//将pArr所指向的动态分配的内存释放
 
    return 0;
}


——模块一:线性结构【把所有的结点用一根线穿起来】

——连续存储【数组】
    1、什么叫数组
        元素类型相同,大小相等
    2、数组的优缺点
        优点:
            存取元素的效率非常高
        缺点:
            事先必须知道数组的长度
            插入删除元素的效率低
            需要大块连续的内存块

=================================================================
——数组的操作:


# include<stdio.h>
# include<stdlib.h>
# include<string.h>
 
 
//定义了一个数据类型,该数据类型的名字叫做struct Arr
//该数据类型含有三个成员,分别是pBase,len,cnt
struct Arr
{
int * pBase;//存储的是数组第一个元素的地址
int len;//数组所能容纳的最大元素的个数
int cnt;//当前数组有效元素的个数
//int increment;//自动增长因子
};
 
//定义方法
void init_arr(struct Arr *,int);//初始化数组
bool append_arr(struct Arr *,int);//追加元素
bool insert_arr(struct Arr *,int value,int index);//插入元素
bool delete_arr(struct Arr *, int index);//删除元素
int get_arr(struct Arr * ,int index);//根据脚标获取元素
bool isEmpty_arr(struct Arr *);//判断是否为空
bool isFull_arr(struct Arr *);//判断数组是否已满
void sort_arr(struct Arr *);//数组排序
void inversion(struct Arr *);//数组倒置
void show_arr(struct Arr *);//打印数组
 
int main(void)
{
    struct Arr arr;//定义结构体变量
    int length = 0;//数组长度
    int number = 0;//追加值变量
    int index = 0;//数组脚标
    int ch = 0;
    while (true)
    {
        printf("请输入您的选项\n");
        printf("1:初始化数组\n");
        printf("2:删除元素\n");
        printf("3:追加元素\n");
        printf("4:插入元素\n");
        printf("5:删除元素\n");
        printf("6:获取元素\n");
        printf("7:数组排序\n");
        printf("8:数组倒序\n");
        scanf("%d",&ch);
 
        switch (ch)
        {
            case 1:
                printf("请输入数组长度:");
                scanf("%d", &length);
                init_arr(&arr, length);
                printf("打印初始化后的数组:\n");
                show_arr(&arr);
                break;
            case 2:
                //删除元素
                printf("请输入要删除元素的脚标:");
                scanf("%d", &index);
                delete_arr(&arr, index);
                printf("打印删除后的数组:\n");
                show_arr(&arr);
                break;
            case 3:
                //追加元素
                printf("请输入要追加的元素:");
                scanf("%d", &number);
                append_arr(&arr, number);
                printf("打印追加后的数组:\n");
                show_arr(&arr);
                break;
            case 4:
                //插入元素
                printf("请输入要插入的元素:");
                scanf("%d", &number);
                printf("请输入要插入的位置:");
                scanf("%d", &index);
                insert_arr(&arr, number, index);
                printf("打印插入元素后的数组:\n");
                show_arr(&arr);
                break;
            case 5:
                //删除元素
                printf("请输入要删除元素的脚标:");
                scanf("%d", &index);
                delete_arr(&arr, index);
                printf("打印删除后的数组:\n");
                show_arr(&arr);
                break;
            case 6:
                //根据脚标获取元素
                printf("请输入要获取元素的脚标:");
                scanf("%d", &index);
                printf("打印获取的元素:%d\n", get_arr(&arr, index));
                break;
            case 7:
                //数组排序
                printf("打印排序后数组:\n");
                sort_arr(&arr);
                show_arr(&arr);
                break;
            case 8:
                //数组倒序
                printf("打印倒序后的数组:\n");
                inversion(&arr);
                show_arr(&arr);
                break;
            default:
                printf("输入不正确,请重新输入:");
        }
    }
    return 0;
}
 
//初始化数组
void init_arr(struct Arr * pArr,int length)
{
    pArr->pBase = (int*)malloc(sizeof(int) * length);
    if (NULL == pArr->pBase)
    {
        printf("动态内存分配失败!");
        exit(-1);
    }
    else
    {
        pArr->len = length;
        pArr->cnt = 0;
        printf("请输入数组元素:\n");
        for (int i = 0; i < length; i++)
        {
            int number;
            scanf("%d",&number);
            pArr->pBase[i] = number;
            pArr->cnt++;
        }
    }
    return;
}
//打印数组
void show_arr(struct Arr * pArr)
{
    if (isEmpty_arr(pArr))
    {
        printf("数组为空\n");
    }
    else
    {
        for (int i = 0; i < pArr->cnt; i++)
        {
            printf("%d",pArr->pBase[i]);
        }
    printf("\n");
    }
}
//判断数组是否为空
bool isEmpty_arr(struct Arr * pArr)
{
    if (0 == pArr->cnt)
        return true;
    else
        return false;
}
//判断数组是否已满
bool isFull_arr(struct Arr * pArr)
{
    if (pArr->cnt == pArr->len)
        return true;
    return false;
}
//追加元素
bool append_arr(struct Arr * pArr, int value)
{
    //如果数组已满返回false
    if (isFull_arr(pArr))
    {
        printf("数组已满\n");
        return false;
    }
    //如果不满就追加
    pArr->pBase[pArr->cnt] = value;
    pArr->cnt++;
    return true;
}
//插入元素
bool insert_arr(struct Arr * pArr, int value, int index)
{
    if (isEmpty_arr(pArr))
    {
        printf("数组为空\n");
        return false;
    }
    //如果数组已满返回false
    if (isFull_arr(pArr))
    {
        printf("数组已满\n");
        return false;
    }
    int i = pArr->cnt;
    for (; i > index; i--)
    {
        pArr->pBase[i] = pArr->pBase[i - 1];
    }
    pArr->pBase[i] = value;
    pArr->cnt++;
    return true;
}
//删除元素
bool delete_arr(struct Arr * pArr, int index)
{
    if (isEmpty_arr(pArr))
    {
        printf("数组为空\n");
        return false;
    }
    if (index > pArr->cnt)
    {
        printf("脚标不存在\n");
        return false;
    }
    int i = index;
    for (;i < pArr->cnt; i++)
    {
        pArr->pBase[i] = pArr->pBase[i + 1];
    }
    pArr->pBase[i] = 0;
    pArr->cnt--;
}
//根据脚标获取元素
int get_arr(struct Arr * pArr, int index)
{
    if (isEmpty_arr(pArr))
    {
        printf("数组为空\n");
        return false;
    }
    if (index > pArr->cnt)
    {
        printf("脚标不存在\n");
        return false;
    }
    return pArr->pBase[index];
}
//数组排序
void sort_arr(struct Arr * pArr)
{
    if (isEmpty_arr(pArr))
    {
        printf("数组为空");
        return;
    }
    for (int i = 0; i < pArr->cnt;i++)
    {
        for (int j = i; j < pArr->cnt;j++)
        {
            if (pArr->pBase[i] > pArr->pBase[j])
            {
                int temp = pArr->pBase[i];
                pArr->pBase[i] = pArr->pBase[j];
                pArr->pBase[j] = temp;
            }
        }
    }
}
//数组倒置
void inversion_arr(struct Arr * pArr)
{
    int i, j, t;
    i = 0;
    j = pArr->cnt - 1;
    while (i < j)
    {
        t = pArr->pBase[i];
        pArr->pBase[i] = pArr->pBase[j];
        pArr->pBase[j] = t;
        i++;
        j--;
    }
}

=================================================================
 
——typedef的用法

# include<stdio.h>
 
typedef int aa;//aa等价于int,可以使用aa来定义int类型变量
 
typedef struct Student
{
    int sid;
    char name[20];
    char sex;
}ST,* PST;
//ST代表结构体,等价于struct Student 类型
//PST代表指针,等价于struct Student * 类型
 
int main(void)
{
    aa a = 1;
    printf("%d\n",a);
 
    ST st = { 10,"111",'男' };
    printf("%d  %s  %c\n", st.sid, st.name, st.sex);
 
    ST * pst = &st;
    printf("%d  %s  %c\n", pst->sid, pst->name, pst->sex);
 
    PST pst2 = &st;
    printf("%d  %s  %c\n", pst2->sid, pst2->name, pst2->sex);
 
    return 0;
}

================================================================= 

    
——离散存储【链表】

        定义:
                N个结点离散分配
                彼此通过指针相连
                每个结点只有一个前驱结点,每个结点只有一个后继结点
                首结点无前驱,尾结点无后继

            术语:
                首结点:
                              第一个存放有效数据的结点
                尾结点:
                              最后一个存放有效数据的结点
                头结点:
                              第一个存放有效数据结点之前的结点
                              头结点并不存放有效数据
                              加头结点的目的主要是为了方便对链表的操作
                              头结点的数据类型和首节点类型一样 
          →   头指针:
          |                    指向头结点的指针变量
          |      尾指针:
          |                    指向尾结点的指针变量
          |
          | __如果希望通过一个函数来对链表进行处理,我们至少需要接收链表的哪些参数?
                    只需要一个参数:头指针。因为我们通过头指针可以推算出链表的其它所有参数
                数据域:
                              存储数据元素信息的区域
                指针域:
                              存储直接后继存储位置的区域
        分类:
                单链表:
                双链表:
                            每一个结点有两个指针域
                循环链表:
                                能通过任何一个结点找到其它所有结点
                非循环链表
        算法:
                遍历
                查找
                清空
                销毁
                求长度
                排序
                删除结点:
                                p->pNext = p->pNext->pNext(错,这样写会导致内存泄漏)
                                free(p->pNext)(错,会导致链表断裂)
                             √ r = p->pNext;    //指向p后面的结点
                                p->pNext = r->pNext;
                                free(r);
                插入结点:把q所指向的结点插到p所指向的结点的后面
                                 1、 r = p->pNext;    //r指向p后面的那个结点
                                       p->pNext = q;    //q是指针变量
                                       q->pNext = r;
                                 2、q->pNext = p->pNext;    //先接后面结点的地址
                                       p->pNext = q;
 

                算法:
                        狭义的算法是与数据的存储方式密切相关。
                        广义的算法是与数据的存储方式无关。
                    泛型:
                            不同的存储方式,执行的操作是一样的。

        链表的优缺点:
                优点:
                            空间没有限制
                            插入删除元素很快
                缺点:
                            存取元素速度慢

=================================================================

——链表的算法操作
# include<stdio.h>
# include<stdlib.h>
 
typedef struct Node
{
    int data;//数据域
    struct Node * pNext;//指针域
}NODE, *PNODE;
//NODE是 struct Node 类型
//PNODE是struct Node * 类型
 
//函数声明
PNODE create_list(void);//创建链表
void traverse_list(PNODE pHead);//遍历链表
bool is_empty(PNODE pHead);//判断链表是否为空
int length_list(PNODE pHead);//求链表长度
bool insert_list(PNODE pHead, int index, int value);//插入元素
bool delete_list(PNODE pHead, int index, int * value);//删除元素
void sort_list(PNODE pHead);//链表排序
 
int main(void)
{
    PNODE pHead = NULL;//等价于 struct Node * pHead = NULL;
    int select = 0;
    int pos = 0;
    int value = 0;
    while (true)
    {
        printf("请选择操作:\n");
        printf("1、创建链表:\n");
        printf("2、遍历链表:\n");
        printf("3、输出链表长度:\n");
        printf("4、链表排序:\n");
        printf("5、插入元素:\n");
        printf("6、删除元素:\n");
        scanf("%d",&select);
        switch (select)
        {
            case 1:
                pHead = create_list();//创建一个非循环单链表并将该链表的头结点返回给pHead
                break;
            case 2:
                traverse_list(pHead);
                break;
            case 3:
                printf("链表长度为:%d\n",length_list(pHead));
                break;
            case 4:
                sort_list(pHead);
                break;
            case 5:
                printf("请输入要插入的值:");
                scanf("%d", &value);
                printf("请输入要插入的位置:");
                scanf("%d", &pos);
                insert_list(pHead, pos, value);
                break;
            case 6:
                printf("请输入要删除的结点脚标:");
                scanf("%d", &pos);
                delete_list(pHead, pos,&value);
                printf("您删除的元素是:%d\n",value);
                break;
            }
            printf("\n");
    }
    return 0;
}
//创建非循环单链表
PNODE create_list(void)
{
    int len = 0;//用来存放有效结点的个数
    int i = 0;//循环变量
    int value = 0;//用来临时存放结点的值
    //创建了一个不存放有效数据的头结点
    PNODE pHead = (PNODE)malloc(sizeof(NODE));
    if (NULL == pHead)//判断创建临时头结点是否成功
    {
        printf("分配失败,程序终止!\b");
        exit(-1);
    }
 
    //创建尾结点,用来追加链表元素
    PNODE pTail = pHead;//把pHead的值赋给pTail,那么pHead和pTail的值都指向头结点
    //将尾结点赋空
    pTail->pNext = NULL;//然后把头结点的指针域清空,这样就可以让pTail永远指向尾结点
 
    printf("请输入您需要生成的链表结点的个数:len = ");
    scanf("%d", &len);
    for (int i = 0; i < len; i++)
    {
        printf("请输入第%d个结点的值:",i+1);
        scanf("%d", &value);
        //定义一个临时结点
        PNODE pNew = (PNODE)malloc(sizeof(NODE));
        if (NULL == pNew)
        {
            printf("分配失败,程序终止!\n");
            exit(-1);
        }
        /*
          pNew->data = value;
         pHead->pNext = pNew;
         pNew->pNext = NULL;
          会导致内存泄露
        */
        //将值赋给临时结点的数据域
        pNew->data = value;
        //将带有数据的临时结点赋给尾结点
        pTail->pNext = pNew;
        //将临时结点指针域赋空
        pNew->pNext = NULL;
        //将临时结点赋给尾结点,相当于尾结点向后移动
        pTail = pNew;
    }
    return pHead;
}
 
//遍历链表
void traverse_list(PNODE pHead)
{
    if (is_empty(pHead))
    {
        printf("链表为空!\n");
        return;
    }
    //定义一个临时结点,将首结点赋给临时结点
    PNODE p = pHead->pNext;
    while (NULL != p)
    {
        //打印该节点的数据
        printf("%d\t", p->data);
        //指针向后移动
        p = p->pNext;
    }
    printf("\n");
    return;
}
 
//判断链表是否为空
bool is_empty(PNODE pHead)
{
    //如果头结点指针域为空,则表示没有首结点
    if (pHead == NULL || NULL == pHead->pNext)
        return true;
    return false;
}
//获取链表长度
int length_list(PNODE pHead)
{
    if (is_empty(pHead))
    {
        printf("链表为空!\n");
        return -1;
    }
    int count = 0;//存储链表结点个数
    PNODE p = pHead->pNext;//定义临时结点,存放首结点
    while (p != NULL)
    {
        p = p->pNext;
        count++;
    }
    return count;
}
//插入元素
//在pHead所指向链表的第pos个结点的前面插入一个新的节点
//该结点的值是value,并且pos的值从1开始
//pos的值不能超过结点个数
bool insert_list(PNODE pHead, int pos, int value)
{
    if (is_empty(pHead))
    {
        printf("链表为空!\n");
        return false;
    }
    if (pos > length_list(pHead))
    {
        printf("结点不存在");
    }
    PNODE p = pHead;
    int i = 0;
    //判断链表是否为空、判断插入位置是否超出链表个数
    //将p指针移动到pos插入位置
    while (NULL != p && i < pos - 1)
    {
        p = p->pNext;
        i++;
    }
    if (i > pos - 1 || NULL == p)
    {
        return false;
    }
    //创建新的结点
    PNODE pNew = (PNODE)malloc(sizeof(NODE));
    if (NULL == pNew)
    {
        printf("动态内存分配失败!\n");
        exit(-1);
    }
    //将值赋给即将要插入的结点
    pNew->data = value;
    //定义一个临时结点,用来存储pos后的结点,防止内存泄露
    PNODE q = p->pNext;//此时q指向了pos结点的后一个结点
    p->pNext = pNew;//将要插入的结点追加到链表的pos位置
    pNew->pNext = q;//连接链表
    return true;
}
//删除元素
bool delete_list(PNODE pHead, int pos, int * value)
{
    PNODE p = pHead;
    int i = 0;
    //判断链表是否为空、判断插入位置是否超出链表个数
    //将p指针移动到pos插入位置
    while (NULL != p && i < pos - 1)
    {
        p = p->pNext;
        i++;
    }
    if (i > pos - 1 || NULL == p)
    {
        return false;
    }
    //将要删除的结点保存,以便释放
    PNODE temp = p->pNext;
    //删除节点
    p->pNext = temp->pNext;
    *value = temp->data;
    //释放节点
    free(temp);
    return true;
}
//链表排序
void sort_list(PNODE pHead)
{
    if (is_empty(pHead))
    {
        printf("链表为空!\n");
    }
    PNODE p1 = pHead->pNext;
    while (p1 != NULL)
    {
        PNODE p2 = p1->pNext;
        while (p2 != NULL)
        {
            if (p1->data > p2->data)
            {
                int temp = p1->data;
                p1->data = p2->data;
                p2->data = temp;
            }
            p2 = p2->pNext;
        }
        p1 = p1->pNext;
    }
}
 
——线性结构的两种常见应用之一    —    栈【先进后出】

        栈(stack) —— 由编译器自动分配施放,存放函数的参数值,局部变量的值等。
        堆(heap) —— 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收
        注意:它与数据结构中的堆是两回事,分配方式类似于链表。

        定义
                一种可以实现“先进后出”的存储结构。

        分类
                静态栈
                动态栈

        算法
                压栈
                出栈

        应用
                函数调用
                中断【计算机组成原理】
                表达式求值
                            一个栈存放运算符,一个栈存放数值,利用两个栈可以做一个计算器。
                内存分配
                缓冲处理
                迷宫

——栈算法演示

        # include <stdio.h>
        # include <stdlib.h>
        # include <malloc.h>
        
        typedef struct Node
        {
        int data;
        struct Node * pNext;
        }NODE, *PNODE;
        
        typedef struct Stack
        {
        PNODE pTop;
        PNODE pBottom;
        }STACK, *PSTACK;
        
        void init_Stack(PSTACK pS);// 初始化栈
        void push_Stack(PSTACK pS, int val);//压栈
        void traverse_Stack(PSTACK pS);// 遍历栈
        bool pop_Stack(PSTACK pS, int * val);//把pS所指向的栈出栈一次,并把出栈的元素存入val形参所指向的变量中,如果出栈失败,
        返回false,否则返回true
        bool empty_Stack(PSTACK pS);//判断栈是否为空
        void clear_Stack(PSTACK pS);//清空栈
        
        int main(void)
        {
        int val;
        
        STACK S; //STACK 等价于 struct Stack
        init_Stack(&S);//初始化    
        
        printf("请输入要压栈的值:\n");
        scanf("%d", &val);
        push_Stack(&S, val);//压栈
        
        printf("请输入要压栈的值:\n");
        scanf("%d", &val);
        push_Stack(&S, val);
        
        printf("遍历输出:\n");//遍历输出
        traverse_Stack(&S);
        
        //清空栈
        clear_Stack(&S);
        printf("清空栈后输出:\n");
        traverse_Stack(&S);
        
        return 0;
        }
        
        void init_Stack(PSTACK pS)
        {
        pS->pTop = (PNODE)malloc(sizeof(NODE));
        if (pS->pTop == NULL)
        {
        printf("动态内存分配失败!\n");
        exit(-1);
        }
        else
        {
        pS->pBottom = pS->pTop;
        pS->pTop->pNext = NULL;
        }
        }
        void push_Stack(PSTACK pS, int val)
        {
        PNODE pNew;
        pNew = (PNODE)malloc(sizeof(NODE));
        pNew->data = val;
        pNew->pNext = pS->pTop;
        pS->pTop = pNew;
        
        return;
        }
        
        void traverse_Stack(PSTACK pS)
        {
        PNODE p = pS->pTop;
        while (p != pS->pBottom)
        {
        printf("%-5d", p->data);
        p = p->pNext;
        }
        printf("\n");
        
        return;
        }
        
        bool pop_Stack(PSTACK pS, int * val)
        {
        if (empty_Stack(pS))//pS本身存放的就是S的地址
        {
        return false;
        }
        else
        {
        PNODE r;
        r = pS->pTop;
        *val = r->data;
        pS->pTop = r->pNext;
        free(r);
        r = NULL;
        return true;
        }
        }
        
        bool empty_Stack(PSTACK pS)
        {
        if (pS->pBottom == pS->pTop)
        return true;
        else
        return false;
        }            
        
        void clear_Stack(PSTACK pS)
        {
        if (empty_Stack(pS))
        {
        return;
        }
        else
        {
        PNODE p = pS->pTop, q = NULL;
        while (p != pS->pBottom)
        {
        q = p->pNext;
        free(p);
        p = q;
        }
        pS->pTop = pS->pBottom;
        return;
        }
        }
    
——线性结构的两种常见应用之二    —    队列【先进先出】

        定义:
                一种可以实现“先进先出”的存储结构。
        分类:
                链式队列    ——    用链表实现
                
                静态队列【数组队列】    ——    用数组实现
                        静态队列通常都必须是循环队列。

                循环队列的讲解:
                    1、静态队列为什么必须是循环队列
                    2、循环队列需要几个参数来确定
                            需要两个参数:
                                    front和rear

                    3、循环队列各个参数的含义
                            这两个参数不同场合有不同的含义:
                                1、队列初始化
                                        front和rear的值都是零;
                                2、队列非空
                                        front代表的是队列的第一个元素;
                                        rear代表的是队列的最后一个有效元素的下一个元素;
                                3、队列为空
                                        front和rear的值相等,但不一定是零。
                            
                    4、循环队列入队伪算法讲解
                            两步完成:
                                1、将值存入rear所代表的位置
                                2、将rear向后移
                                        错误的写法:rear = rear + 1;
                                        正确的写法:rear = (rear + 1) % 数组的长度

                    5、循环队列出队伪算法讲解
                                front = (front + 1) % 数组长度

                    6、如何判断循环队列是否为空
                                front = rear,则该队列为空。

                    7、如何判断循环队列是否已满
                            1、多增加一个标识参数
                            2、少用一个元素【通常使用第二种方式】
                                    如果rear和front相邻,则队列已满
                                        用C语言为算法表示就是:
                                            if ( (rear + 1) % 数组长度 == front)
                                                    队列已满;
                                            else
                                                    队列不满;
                队列算法:
                                入队
                                出队

                队列的具体应用:
                                所有和时间有关的操作都与队列有关。
                                例如系统任务,先打开先执行。

——递归

        定义:
                一个函数直接或间接的调用函数本身。【用栈来实现】

        函数的调用:
                当在一个函数的运行期间内调用另一个函数时,在运行被调函数之前,系统需要完成三件事:
                    1、将所有的实际参数,返回地址等信息传递给被调函数保存。
                    2、为被调函数的局部变量(也包括形参)分配存储空间。
                    3、将控制转移到被调函数的入口。
                从被调函数返回主调函数之前,系统也要完成三件事:
                    1、保存被调函数的返回结果。
                    2、释放被调函数所占的存储内存。
                    3、依照被调函数保存的返回地址将控制转移到被调函数。
                当有多个函数相互调用时,按照”后调用先返回(栈)“的原则,上述函数之间信息传递和控制转移必须借助”栈“来实现,即
                系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就在栈顶分配一个存储区,进行压栈操作,
                当前运行的函数永远都在栈顶位置。

        递归要满足的三个条件:
                1、递归必须要有一个明确的终止条件。
                2、该函数所处理的数据规模必须在递减。
                3、这个转化必须是可解的。

        循环和递归
            递归:
                    易于理解
                    速度慢
                    存储空间大
            循环:
                    不易理解
                    速度快
                    存储空间小

        递归的应用:
            树和森林就是以递归的方式来定义的
            树和图的很多算法都是以递归来实现的
            很多数学公式就是以递归的方式定义的
                    斐波那契数列

        举例:
                1、求阶乘
        # include <stdio.h>
 
        long f(long n)
        {
            if (1 == n)
                return 1;
            else
                return f(n-1) * n;
        }
        
        int main (void)
        {
            int val;
            scanf("%d",&val);
            printf("%d的阶乘位:%d\n",val,f(val));
        }
 
                2、1+2+3+……+100的和
        # include <stdio.h>
        
        int fun(int n)
        {
        
        if (1 == n)
        return 1;
        else
        return n += fun(n-1);
        }
 
        int main(void)
        {
        printf("%d\n",fun(100));
        }
 
                3、汉诺塔
                4、走迷宫

——模块二:非线性结构

    树:
        树的定义:
                        1、有且只有一个称为根的节点。
                        2、有若干个互不相交的子树,这些子树本身也是一棵树。
                术语:
                        节点
                        父节点
                        子节点
                        子孙
                        堂兄弟
                        深度:
                                树中结点的最大层次(从根节点到最底层节点的层数称之为深度)。
                                根节点是第一层。
                        叶子节点:
                                没有子结点的结点。
                        非终端节点:
                                实际就是非叶子结点。
                        度:
                                子节点的个数称为度。
        树的分类:
                        一般树:
                                    任意一个节点的子节点的个数都不受限制。
                        二叉树:
                                    任意一个节点的子节点的个数最多两个,且子节点的位置不可更改。
                        二叉树的分类:
                                    一般二叉树:
                                    满二叉树:在不增加树的层数的前提下,无法再多添加一个节点的二叉树就是满二叉树。
                                    完全二叉树:如果只是删除了满二叉树最底层最右边的连续若干个结点,这样形成的二叉树就是完全二叉树。
                                    完全二叉树包含满二叉树。
                        森林:
                                    n个互不相交的树的集合。

        树的存储:
                        二叉树的存储:
                                连续存储【完全二叉树】
                                    优点:
                                            查找某个结点的父结点和子结点(也包括判断有没有父结点和子结点)的速度很快。
                                    缺点:
                                            耗用内存空间过大。
                                连式存储:
                                            

                        一般树的存储:
                                双亲表示法:存储父结点的下标,求父结点方便。
                                孩子表示法:存储子结点的指针,求子结点方便。
                                双亲孩子表示法:存储父结点的下标和子结点的指针,求父结点和子结点都很方便。
                                二叉树表示法:把一个普通树转化成二叉树来存储。
                                    具体转换方法:
                                                    设法保证任意一个结点的左指针域指向他的第一个子结点,右指针域指向它的兄弟结点,只要能满足
                                                    此条件,就能把一个普通树转化为二叉树。一个普通树转化成的二叉树一定没有右子树。
                        森林的存储:
                                先把森林转化为二叉树,再存储为二叉树

        二叉树的操作:
                        遍历:
                                先序遍历:
                                                先访问根结点
                                                再访问左子树
                                                再访问右子树   
                                中序遍历:
                                                先遍历左子树
                                                再访问根节点
                                                再遍历右子树
                                后序遍历:
                                                先遍历左子树
                                                再遍历右子树
                                                再访问根节点
                                示例1:
                                        先序:ABCDEFGH
                                        中序:BDCEAFHG
                                        求后序:DECBHGFA
                                示例2:
                                        先序:ABDGHCEFI
                                        中序:GDHBAECIF
                                        求后序:GHDBEIFCA
                                示例:
                                        中序:BDCEAFHG
                                        后序:DECBHGFA
                                        求先序:ABCDEFGH

                        已知两种遍历序列,求原始二叉树:
                                通过【先序和中序】或者【中序和后序】我们可以还原出原始的二叉树,但是通过【先序和后序】是无法还原出
                                原始的二叉树的。

        树的应用:
                        树是数据库中数据组织的一种重要形式
                        操作系统子父进程的关系本身就是一棵树
                        面向对象语言中类的继承关系
                        哈夫曼树

——二叉树的先中后序遍历

        # include <stdio.h>
        # include <stdlib.h>    
        
        struct BTNode
        {
        char data;
        struct BTNode * pLchild;//p是指针  L是左  child是孩子
        struct BTNode * pRchild;
        };
        
        struct BTNode * CreateBTree(void);//创建一个二叉树    
        void PreTraverseBTree(struct BTNode * pT);//先序遍历
        void InTraverseBTree(struct BTNode * pT);//中序遍历
        void PostTraverseBTree(struct BTNode * pT);//后序遍历
        
        
        int main(void)
        {
        struct BTNode * pT = CreateBTree();//造出一个链式二叉树,并且返回二叉树的首地址
        PreTraverseBTree(pT);
        InTraverseBTree(pT);
        PostTraverseBTree(pT);    
        
        return 0;
        }
        
        struct BTNode * CreatBTree(void)
        {
        //创建五个子结点
        struct BTNode * pA = (struct BTNode *)malloc(sizeof(struct BTNode));
        struct BTNode * pB = (struct BTNode *)malloc(sizeof(struct BTNode));
        struct BTNode * pC = (struct BTNode *)malloc(sizeof(struct BTNode));
        struct BTNode * pD = (struct BTNode *)malloc(sizeof(struct BTNode));
        struct BTNode * pE = (struct BTNode *)malloc(sizeof(struct BTNode));
        //子结点赋值
        pA->data = 'A';
        pB->data = 'B';
        pC->data = 'C';
        pD->data = 'D';
        pE->data = 'E';
        //子结点连接指针域
        pA->pLchild = pB;
        pA->pRchild = pB;
        pB->pLchild = pB->pRchild = NULL;
        pC->pLchild = pD;
        pC->pRchild = NULL;
        pD->pLchild = NULL;
        pD->pRchild = pE;
        pE->pLchild = pE->pRchild = NULL;
        
            return pA;
        
        }    
        
        void PreTraverseBTree(struct BTNode * pT)//先序遍历
        {
        if (NULL != pT->pLchild)
        {
        printf("%c\n", pT->data);
        if (NULL != pT->pLchild)
        {
        PreTraverseBTree(pT->pLchild);
        }
        if (NULL != pT->pRchild)
        {
        PreTraverseBTree(pT->pRchild);
        }
        }
        }
        
        void InTraverseBTree(struct BTNode * pT)//中序遍历
        {
        if (NULL != pT)
            {
        
        if (NULL != pT->pLchild)
        {
        InTraverseBTree(pT->pLchild);
        }
        printf("%c\n", pT->data);    
        
        if (NULL != pT->pRchild)
        {
        InTraverseBTree(pT->pRchild);
        }
        }
        }
        
        void PostTraverseBTree(struct BTNode * pT)//后序遍历
        {
        if (NULL != pT)
        {
        
        if (NULL != pT->pLchild)
        {
        PostTraverseBTree(pT->pLchild);
        }
        
        if (NULL != pT->pRchild)
        {
        PostTraverseBTree(pT->pRchild);
        }
        printf("%c\n", pT->data);
        }
        }

    图:呵呵

——模块三:查找和排序

    折半查找
     排序:排序算法的优秀与否取决于算法的时间、空间和稳定性
            冒泡排序
        # include <stdio.h>
        
        int main (void)
        {
        int i,j,a[5],temp;
        printf("请输入五个数字:\n");
        for (i=0; i<5; i++)
        {
        scanf ("%d",&a[i]);
        }
        for (i=1; i<6; i++)
        {
        for (j=0; j<5-i; j++)
        {
        if (a[j] > a[j+1])
        {
        temp = a[j];
        a[j] = a[j+1];
        a[j+1] = temp;
        }
        }
        }
        printf("输出排序后的数字:\n");
        for (i=0; i<5; i++)
        {
        printf("%-4d",a[i]);
        }
        
        return 0;
        }
            插入排序
            选择排序
        # include <stdio.h>
        
        void sort(int * a, int len)
        {
        int i, j, min, t;
        for (i = 0; i < len - 1; i++)
        {
        for (min = i, j = i + 1; j<len; j++)
        {
        if (a[min]>a[j])
        min = j;
        }
        if (min != i)
        {
        t = a[i];
        a[i] = a[min];
        a[min] = t;
        }
        }
        }
        
        int main(void)
        {
        int a[6] = { 5, 6, 2, 7, 9, 3 };
        sort(a, 6);
        for (int i = 0; i < 6; i++)
            printf("%-4d", a[i]);
        printf("\n");
        
        return 0;
        }
            快速排序
            归并排序

    排序和查找的关系
            排序是查找的前提
            排序是重点

Java中容器和数据结构的相关知识
    Iterator接口
    Map
            哈希表 

再次讨论什么是数据结构
        数据结构研究的是数据的存储和数据的操作的一门学问
        数据的存储分为两部分
                个体的存储
                个体关系的存储
                从某个角度而言,数据的存储最核心的就是个体关系的存储,个体的存储可以忽略不计。

再次讨论什么是泛型
        同一种逻辑结构,无论该逻辑结构是如何实现物理存储的,我们都可以对它执行相同的操作。
posted @ 2017-02-07 17:06  WWWYC  阅读(239)  评论(0编辑  收藏  举报