zoukankan      html  css  js  c++  java
  • 数据结构(暂时完结)

    时间: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 ", pst->sid, pst->name, pst->age);
    }
     
    int main(void)
    {
        struct Student st;//已经为st分配了内存空间,只不过内存中没有值。例如 int i;i已经有内存空间。
        fun(&st);
        //printf("%d %s %d ",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 ",*pArr,pArr[1]);
        //可以把pArr当做一个普通数组来使用
        for (int i = 0; i < len; i++)
        {
            scanf("%d", &pArr[i]);
        }
        //输出
        for (int i = 0; i < len; i++)
        {
            printf("%d ", *(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("请输入您的选项 ");
            printf("1:初始化数组 ");
            printf("2:删除元素 ");
            printf("3:追加元素 ");
            printf("4:插入元素 ");
            printf("5:删除元素 ");
            printf("6:获取元素 ");
            printf("7:数组排序 ");
            printf("8:数组倒序 ");
            scanf("%d",&ch);
     
            switch (ch)
            {
                case 1:
                    printf("请输入数组长度:");
                    scanf("%d", &length);
                    init_arr(&arr, length);
                    printf("打印初始化后的数组: ");
                    show_arr(&arr);
                    break;
                case 2:
                    //删除元素
                    printf("请输入要删除元素的脚标:");
                    scanf("%d", &index);
                    delete_arr(&arr, index);
                    printf("打印删除后的数组: ");
                    show_arr(&arr);
                    break;
                case 3:
                    //追加元素
                    printf("请输入要追加的元素:");
                    scanf("%d", &number);
                    append_arr(&arr, number);
                    printf("打印追加后的数组: ");
                    show_arr(&arr);
                    break;
                case 4:
                    //插入元素
                    printf("请输入要插入的元素:");
                    scanf("%d", &number);
                    printf("请输入要插入的位置:");
                    scanf("%d", &index);
                    insert_arr(&arr, number, index);
                    printf("打印插入元素后的数组: ");
                    show_arr(&arr);
                    break;
                case 5:
                    //删除元素
                    printf("请输入要删除元素的脚标:");
                    scanf("%d", &index);
                    delete_arr(&arr, index);
                    printf("打印删除后的数组: ");
                    show_arr(&arr);
                    break;
                case 6:
                    //根据脚标获取元素
                    printf("请输入要获取元素的脚标:");
                    scanf("%d", &index);
                    printf("打印获取的元素:%d ", get_arr(&arr, index));
                    break;
                case 7:
                    //数组排序
                    printf("打印排序后数组: ");
                    sort_arr(&arr);
                    show_arr(&arr);
                    break;
                case 8:
                    //数组倒序
                    printf("打印倒序后的数组: ");
                    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("请输入数组元素: ");
            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("数组为空 ");
        }
        else
        {
            for (int i = 0; i < pArr->cnt; i++)
            {
                printf("%d",pArr->pBase[i]);
            }
        printf(" ");
        }
    }
    //判断数组是否为空
    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("数组已满 ");
            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("数组为空 ");
            return false;
        }
        //如果数组已满返回false
        if (isFull_arr(pArr))
        {
            printf("数组已满 ");
            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("数组为空 ");
            return false;
        }
        if (index > pArr->cnt)
        {
            printf("脚标不存在 ");
            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("数组为空 ");
            return false;
        }
        if (index > pArr->cnt)
        {
            printf("脚标不存在 ");
            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 ",a);
     
        ST st = { 10,"111",'男' };
        printf("%d  %s  %c ", st.sid, st.name, st.sex);
     
        ST * pst = &st;
        printf("%d  %s  %c ", pst->sid, pst->name, pst->sex);
     
        PST pst2 = &st;
        printf("%d  %s  %c ", 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("请选择操作: ");
            printf("1、创建链表: ");
            printf("2、遍历链表: ");
            printf("3、输出链表长度: ");
            printf("4、链表排序: ");
            printf("5、插入元素: ");
            printf("6、删除元素: ");
            scanf("%d",&select);
            switch (select)
            {
                case 1:
                    pHead = create_list();//创建一个非循环单链表并将该链表的头结点返回给pHead
                    break;
                case 2:
                    traverse_list(pHead);
                    break;
                case 3:
                    printf("链表长度为:%d ",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 ",value);
                    break;
                }
                printf(" ");
        }
        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("分配失败,程序终止!");
            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("分配失败,程序终止! ");
                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("链表为空! ");
            return;
        }
        //定义一个临时结点,将首结点赋给临时结点
        PNODE p = pHead->pNext;
        while (NULL != p)
        {
            //打印该节点的数据
            printf("%d ", p->data);
            //指针向后移动
            p = p->pNext;
        }
        printf(" ");
        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("链表为空! ");
            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("链表为空! ");
            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("动态内存分配失败! ");
            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("链表为空! ");
        }
        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("请输入要压栈的值: ");
            scanf("%d", &val);
            push_Stack(&S, val);//压栈
            
            printf("请输入要压栈的值: ");
            scanf("%d", &val);
            push_Stack(&S, val);
            
            printf("遍历输出: ");//遍历输出
            traverse_Stack(&S);
            
            //清空栈
            clear_Stack(&S);
            printf("清空栈后输出: ");
            traverse_Stack(&S);
            
            return 0;
            }
            
            void init_Stack(PSTACK pS)
            {
            pS->pTop = (PNODE)malloc(sizeof(NODE));
            if (pS->pTop == NULL)
            {
            printf("动态内存分配失败! ");
            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(" ");
            
            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 ",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 ",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 ", 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 ", 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 ", pT->data);
            }
            }

        图:呵呵

    ——模块三:查找和排序

        折半查找
         排序:排序算法的优秀与否取决于算法的时间、空间和稳定性
                冒泡排序
            # include <stdio.h>
            
            int main (void)
            {
            int i,j,a[5],temp;
            printf("请输入五个数字: ");
            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("输出排序后的数字: ");
            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(" ");
            
            return 0;
            }
                快速排序
                归并排序

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

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

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

    再次讨论什么是泛型
            同一种逻辑结构,无论该逻辑结构是如何实现物理存储的,我们都可以对它执行相同的操作。
  • 相关阅读:
    window.location.Reload()和window.location.href 区别
    PHP substr(),mb_substr()及mb_strcut的区别和用法
    jstree节点展开设置
    关于Jquery中ajax方法data参数用法
    HTML相对路径(Relative Path)和绝对路径(Absolute Path)
    Win32基础编程了解窗口类
    Visual C++ ActiveX 开发指南:第一章 什么是ActiveX
    分粥
    蛙蛙推荐:ASP实现自定义标签模板
    蛙蛙请教:如何利用委托实现多个方法同时调用.
  • 原文地址:https://www.cnblogs.com/wwwwyc/p/6374988.html
Copyright © 2011-2022 走看看