数据结构整理笔记

数据结构与算法

数据结构:数据的组成形式(数据是以什么样的形式组织起来的,数组、链表、队列、树、图等)

算法(注:强调的是数据结构与算法中的算法,狭义算法):对所存储数据的操作(操作指的是对于所存数据有关问题,求解最终答案的过程)的方法,例:[1、2、3、4、5]中的最大值,求得最大值的方法(一系列操作)就是算法

书籍推荐

数据结构概述(教材选用严蔚敏、吴伟民,该书程序是伪算法具体的程序是高一凡,西电的,大牛,只有程序。还有一本书,台湾的黄国瑜自己写的只有思路,程序是另外一个合作的清华的写的,可惜很多错的。)学完数据结构之后会对面向过程的函数有一个更深的了解,有本通俗易懂的数据结构的书《大话数据结构》用来入门很不错。

数据结构的概述

定义

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

   数据结构=个体+个体的关系

   算法=对存储数据的操作

        狭义:

             数据结构是专门研究数据存储的问题

             数据的存储包含两方面:个体的存储 + 个体关系的存储

        广义:

             数据结构既包含数据的存储也包含数据的操作

             对存储数据的操作就是算法

算法

狭义:

    算法是和数据的存储方式密切相关

广义:

    算法和数据的存储方式无关,这就是泛型思想

算法的真正学法:

很多算法你根本解决不了!!!!!!因为很多都属于数学上的东西,所以我们把答案找出来,如果能看懂就行,但是大部分人又看不懂,分三步,按照流程,语句,试数。这个过程肯定会不断地出错,所以不断出错,不断改错,这样反复敲很多次,才能有个提高。实在看不懂就先背会。      

衡量算法的标准:

(1) 时间复杂度

大概程序要执行的次数,而并非是执行的时间(因为同一程序在不同机器上执行的时间是不一样的,有差异)

(2) 空间复杂度

   算法执行过程中大概所占用的最大内存

(3) 难易程度(主要是应用方面看重)

(4) 健壮性(不能别人给一个非法的输入就挂掉)

        数据结构的地位:

              数据结构是软件中最核心的课程

              程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言

 泛型

             对于同一种逻辑结构,无论该逻辑结构的物理存储是什么样子的,我们可以对它执行相同的操作。

             泛型:(给你一种假象,只不过牛人从内部都弄好了)利用某种技术达到的效果就是:不同的存储方式,执行的操作是一样的

预备知识

    指针

     指针的重要性:(内存是可以被CPU直接访问的,硬盘不行;cpu访问内存主要靠地址总线,数据总线,控制总线。)

     指针是C语言的灵魂

     定义

     地址:

         地址就是内存单元的编号

         从0开始的非负整数

         范围:0--FFFFFFFF[0-4G-1](地址线是32位,刚好控制2的32次)

     指针:

     指针就是地址  地址就是指针

     指针变量是存放内存单元地址的变量

     指针的本质是一个操作受限的非负整数(不能加乘除,只能减)

     分类:

         1、基本类型的指针

         2、指针和数组的关系

     结构体(C++中用类也能实现)

     为什么会出现结构体

     为了表示一些复杂的数据,而普通的基本类型变量无法满足要求

     什么叫结构体

     结构体是用户根据实际需要自己定义的复合数据类型

     如何使用结构体

     两种方式:

     struct Student st = {1000, "zhangsan", 20}

     struct Student * pst = &st;

     st.sid

     pst->sid

     pst所指向的结构体变量中的sid这个成员

     注意事项:

     结构体变量不间能加减乘除,但可以相互赋值

     普通结构体变量和结构体指针变量作为函数参数的传递

 (病毒就是靠访问正在运行的那些程序所占用的内存。Java中规定局部变量必须初始化,因为这些变量一开始都是垃圾值,但是属性不是必须初始化的,因为已经默认初始化为0)动态内存分配和释放(动态分配的内存一定要手动释放,否则造成内存泄露。)(java中A aa = new A();其实就是 A *p = (A *)malloc(sizeof(A)))

数据存储结构

线性结构【把所有的结点用一根直线穿起来】

数组和链表

栈和队列是数组和链表这两种线性结构的应用

连续存储【数组】

1、什么叫做数组

元素类型相同,大小相等(数组传参,只要传进去首地址和长度就行)

2、数组的优缺点:

优点:

存取速度快

缺点:

事先必须知道数组的长度

插入删除元素很慢

空间通常是有限制的

需要大块连续的内存块

插入删除元素的效率很低

 数组代码

离散存储【链表】(我们搞底层的开发,类似于SUN公司的类)

     定义:

     n个节点离散分配

     彼此通过指针相连

     每个节点只有一个前驱节点,每个节点只有一个后续节点

     首节点没有前驱节点,尾节点没有后续节点。

     专业术语:

     首节点:

     第一个有效节点

     尾节点:

     最后一个有效节点

     头节点:

     头结点的数据类型和首节点的类型一样

     没有存放有效数据,最最前面的,是在

     首节点之前的,主要是为了方便对链表

     的操作。

     头指针:(指向头)

     指向头节点的指针变量

     尾指针:

     指向尾节点的指针

(头结点有可能很大,占的内存可能大,假设我想造一个函数输出所有链表的值,那你如果不用头指针类型做形参,那由于不同链表的头节点不一样大小,这样就没办法找出形参)

     确定一个链表需要几个参数:(或者说如果期望一个函数对链表进行操作我们至少需要接收链表的那些信息???)

      只需要一个参数:头指针,因为通过它我们可以推出链表的所有信息。

(链表的程序最好一定要自己敲出来)

     分类:

     单链表

     双链表:

     每一个节点有两个指针域

     循环链表

     能通过任何一个节点找到其他所有的节点

     非循环链表

(java中变成垃圾内存则会自动释放,但是C和C++则不会,所以要手动释放,否则会引起内存泄露。delete等于free)  

     算法:

     遍历

     查找

     清空

     销毁

     求长度

     排序

     删除节点

     插入节点

链表的优缺点:

     优点:

     空间没有限制

     插入删除元素很快

     缺点:

     存取速度很慢。

链表代码

栈和队列是一种特殊的线性结构,是连续存储或离散存储的一种应用

线性结构的应用------

定义:一种可以实现“先进后出“的存储结构,类似于箱子

分类:

    静态栈

          动态栈

算法:

         出栈

         压栈  

应用:

         函数调用

         中断

         表达式求值

         内存分配

         缓冲处理

         迷宫

int main(void)
{
  int p;                          
  int * m = (int *)malloc(100);
}

如静态变量p和m是在栈中分配,有操作系统自动分配和释放。而(int *)malloc(100);执行后,将在堆中分配一块100字节的内存,由程序员手动分配。

栈的示意图

 栈代码演示

线性结构的应用------队列

定义:

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

分类

        链式队列: -----用链表实现(比较简单)

        静态队列: -----用数组实现  静态队列通常都必须是循环队列

应用:

         所有和时间有关的事件都有队列的影子

循环队列的讲解

1)静态队列为什么必须是循环队列

现在如果一个数组里面存了四个元素,那么front就只想第一个有效元素,而real指向最后一个元素的下一个元素,
当增加元素时,只能在rear一端增加,即rear向上移。
删除元素时,只能在front一端删除元素,即front向上移。
但是如果一直增增删删,那么就会造成rear端溢出,而front端浪费,
所以对于这种情况,可以采用循环队列的形式,即当rear已经指向数组最后一个元素时,那么就可以转而将rear指向数组的第一个空出来的空间

2)循环队列需要几个参数来确定

         需要2个参数来确定:front rear                          

3)循环队列各个参数的含义:

          2个参数在不同场合有不同的含义

  • 队列初始化

    frontrear的值都为零

  • 队列非空

    front代表的是队列的第一个元素

    rear代表的是队列的最后一个有效元素的下一个元素

  • 队列为空

    frontreal的值相等,但不一定为零

4)循环队列入队伪算法讲解:

       两步完成:

    •  将值存入rear所代表的位置
    •  错误的写法 :rear = rear+1;

    正确的写法是:rear =  (rear+1)%数组的长度

5)循环队列出队伪算法讲解

                        Front =front +1%数组的长度

  (6) 如何判断循环队列是否为空

          如果frontrear的值相等,则该队列就一定为空

7)如何判断循环队列是否已满

       因为front的值可能比rear大,也可能比他小,也可能相等

    所以有两种方式:

  • 多增加一个标识是否满的参数
  • 少用一个元素【通常用此种方式】

 如果frontrear的值相差1,且front>rear,则证明队列已满。

      用C语言伪算法表示为:

        if ((rear+1)%数组长度==front)

           已满

        else

           未满

循环队列代码演示

专题:递归:

知识点一:函数的调用

当在一个函数的运行期间调用另一个函数时,在运行被调函数之前,系统需要完成三件事:

  • 将所有的实际参数,返回地址等信息传递给被调函数。
  • 为被调函数的局部变量(也包括形参)分配存储空间
  • 将控制转移到被调函数的入口

 从被调函数返回主调函数之前,系统也要完成三件事:

  • 保存被调函数的返回结果
  • 释放被调函数所占的存储空间
  • 依照被调函数保存的返回地址将控制转移到调用函数

当有多个函数相互调用时,按照“后调用先返回”的原则,上述函数之间信息传递和控制转移必须借助“栈”来实现,即系统将整个程序 运行时所需的数据空间安排在一个栈中,每当调用一个函数时,将在栈顶分配一个存储区,进行压栈操作,每当一个函数退出时,就释放它的存储区,就进行出栈操作,当前运行的函数永远都在栈顶位置。

A函数调用A函数和A函数调用B函数在计算机看来是没有任何区别的,只不过用我们日常的思维方式比较怪异而已。 

知识点二:递归必须满足的三个条件

  •  递归必须得有一个明确的终止条件
  •  该函数所处理的数据规模必须在递减
  •  这个转化必须是可解的

知识点三:递归和循环的优缺点比较

  •  递归:
    1. 易于理解
    2. 速度慢
    3. 存储空间大
  •  循环:
    1.  不易理解
    2.   速度快
    3.   存储空间小

知识点四:递归的应用

  •  树和森林就是以递归的方式定义的
  •  树和图的很多算法都是以递归来实现的
  •  很多数学公式就是以递归的方式定义的
  • 斐波拉契序列:1 2 3 5 8 13 21 34

 模块二:非线性结构

(现在人类还没有造出一个容器,能把树和图都装进去的,因为他们确实是太复杂了)(都要靠链表去实现)

树定义

专业定义:

  1、有且只有一个称为根的节点

  2、有若干个互不相交的子树,这些子树本身也是一棵树

通俗定义:

1、树是由节点和边组成

2、每个节点只有一个父节点但可以有多个子节点

3、但有一个节点例外,该节点没有根节点,此节点称为根节点

专业术语

节点    父节点      子节点

子孙    堂兄弟      

深度:

从根节点到最底层节点的层数称之为深度

根节点是第一层

叶子节点;(叶子就不能劈叉了)

没有子节点的节点

非终端节点:

实际就是非叶子节点。

根节点既可以是叶子也可以是非叶子节点

度:

子节点的个数称为度。(一棵树看最大的)

树分类:

一般树

任意一个节点的子节点的个数都不受限制

二叉树(有序树)

任意一个节点的子节点的个数最多两个,且子节点

的位置不可更改。

分类:

一般二叉树

满二叉树

在不增加树的层数的前提下。无法再多

添加一个节点的二叉树就是满二叉树。

完全二叉树

如果只是删除了满二叉树最底层最右边的

连续若干个节点,这样形成的二叉树就是

完全二叉树。

森林

n个互不相交的树的集合

一般的二叉树要以数组的方式存储,要先转化成完全二叉树,因为如果你

只存有效节点(无论先序,中序,后序),则无法知道这个树的组成方式

是什么样子的。

树的存储(都是转化成二叉树来存储)

二叉树的存储

连续存储【完全二叉树】

优点:

查找某个节点的父节点和子节点(也包括判断有咩有)速度很快

缺点:

耗用内存空间过大

链式存储

一般树的存储

双亲表示法

  求父节点方便

孩子表示法

  求子节点方便

双亲孩子表示法

  求父节点和子节点都很方便

二叉树表示法

把一个普通树转化成二叉树来存储

具体转换方法:

设法保证任意一个节点的

左指针域指向它的第一个孩子

有指针域指向它的下一个兄弟

只要能满足此条件,就可以把一个普通树转化成二叉树

一个普通树转化成的二叉树一定没有右子树

森林的存储

    先把森林转化为二叉树,再存储二叉树,具体方式为:根节点之间可以当成是兄弟来看待

二叉树操作

遍历

                      先序遍历【先访问根节点】

                       先访问根节点

                       再先序访问左子树

                       再先序访问右子树

                      中序遍历【中间访问根节点】

                       中序遍历左子树

                       再访问根节点

                       再中序遍历右子树

                      后序遍历【最后访问根节点】

                       先后序遍历左子树

                       再后序遍历右子树

                       再访问根节点

已知两种遍历序列求原始二叉树

通过先序和中序 或者 中序和后续我们可以还原出原始的二叉树但是通过先序和后续是无法还原出原始的二叉树的

换种说法:

只有通过先序和中序, 或通过中序和后序我们才可以唯一的确定一个二叉树     

应用

树是数据库中数据组织的一种重要形式(例如图书馆的图书分类一层一层往下分。)

操作系统子父进程的关系本身就是一棵树

面向对象语言中类的继承关系本身就是一棵树赫夫曼树(树的一个特例)

最小生成树

最短路径

拓扑排序

关键路径

查找

查找的基本概念

线性表的查找

树表查找

哈希表查找

排序

 排序

插入排序

  • 直接插入排序
  • 二分插入排序(折半插入排序)
  • 希尔排序

交换排序

  • 冒泡排序
  • 快速排序

选择排序

  • 简单选择排序
  • 堆排序

归并排序

基数排序

排序代码

posted @ 2021-07-11 14:41  北极星!  阅读(893)  评论(0编辑  收藏  举报