zoukankan      html  css  js  c++  java
  • 数据结构笔记

    数据结构和算法

    1:程序设计=数据结构+算法。 

       数据结构就是关系,就是数据元素之间的一个或多种特定关系。

       数据结构:1)逻辑结构(集合结构,线性结构,树形结构,图形结构),2)物理结构(数据存储方式,顺序存储,链式存储)。

    2:算法:解决特定问题求解步骤的描述,计算机中表现为多个指令的有限序列,并且每条指令表示一个或多个操作。

       算法的特性:输入(0个或多个),输出(至少一个),有穷(有限步内完成),确定(无歧义),可行(每一步都是可行的)。 

    3:算法效率的度量方法:效率或执行时间。

       影响因素:1)算法策略;2)编译产生的代码质量;3)问题的输入规模;4)机器的指令执行速度;

       判断效率时,关注最高项即可(忽略次要项以及系数)。

    4:时间复杂度和空间复杂度:O(),又称大O记法。一般情况下,随着输入规模n的增大,T(n)增长最慢的算法为最优算法。0(1)<0(logn)<O(n)<0(nlogn)<0(n^2)<0(n^3)<0(2^n)<0(n!)<0(n^n),

       时间复杂度表示运行时间的需求,空间复杂度表示空间需求。

    5:线性表:零个或多个元素组成的有限序列。(第一个无前驱,最后一个无后继,其余元素都有一个前驱和后继。元素的个数为n。)

       数据类型:是指一组性质相同的值的集合及定义在此集合上的一些操作的总称。例如,编程语言中的整型,浮点型等。

       抽象数据类型ADT:对数据类型进行抽象,抽取出事务具有的普遍性的本质,是特征的概括,而不是细节。(数据类型和相关的操作捆绑在一起)

       线性表抽象数据类型(List):

       Data:线性表的数据对象集合为{a,b,c...},每一个元素的数据类型为DataType

       Operation: InitList(*L),初始化,判断,取值,返回,插入...

    6:线性表的顺序存储结构:类似于数组,元素依次排放。顺序结构封装的三个属性:数据起始地址,线性表最大长度,当前线性表长度。线性表是从1开始,而数组是从0开始。
       插入和删除:首先判断插入删除的位置是否合理,然后插入是从插入位置开始向后移动一位,完成后插入元素,删除则是去除需要删除的元素,然后后面的元素开始依次向前移动一位。

       线性表的读取为0(1),插入和删除都是0(n).

       优点:无需为表示表中的元素之间的逻辑关系而增加额外的存储空间,可以快速的存取表中的任意位置的元素。

       缺点:插入和删除时需要移动大量元素,当线性表长度变化较大时,难以确定存储空间的容量。

    7:线性表的链式存储结构:每个元素多用一个位置来存放指向下一个元素位置的指针,依次类推,可以找到所有的元素。现在的链式存储中,除了要存储数据本身外,还要存储它的后继元素的存储地址(指针)。

       数据域:存储数据信息的域;指针域:存储直接后继位置的域。这两部分信息组成数据元素称之为存储映像,节点Node。

       链表中每个结点中只包含一个指针域,为单链表。链表中的第一个界点的存储位置叫做头指针,最后一个界点指针为空。

    8:头节点和和头指针:头节点一般不存储任何数据信息。

       头指针:

       头指针是指链表指向第一个结点的指针,若链表有头节点,则是指向头结点的指针。

       头指针具有标识作用,常用头指针冠以链表的名字(指针变量的名字)。

       无论链表是否为空,头指针均不为空。

       头指针是链表的必要元素。

       头节点:

       放在第一个元素的结点之前,其数据域一般无意义(可以用来存放链表长度),是为了操作的统一方便设立(在第一元素结点前插入和删除第一结点与其他结点操作统一)。

       头节点不一定是链表的必要元素。

     

           单链表的读取?在线性表顺序存储结构中,计算任意一个元素的存储位置是很容易的,但在单链表结构中,第i个元素的如何获取?--》工作指针后移

    9:单链表的插入:

     只需要更改ai和s节点的指针域:应该首先更改结点s的指针域!

    s->next=p->next;
    p->next=s;

    code:

     

     p->next=p->next->next;或q=p->next; p->next=q->next;

    对于插入或删除数据越频繁的操作,单链表的效率优势越明显。

    10:单链表的整表创建

       顺序存储结构的线性表可以类比于数组的初始化,单链表的数据分散在内存在各个角落。创建单链表是一个动态生成链表的过程。

        头插法:从一个空表开始,生成新节点,读取的数据放到新节点的数据域中,然后将新节点插入到当前链表的表头上。也就是说,把新加的元素放在表头后的第一个位置。

      

     头插法表中的元素顺序是与原来相反的。

    尾插法:把新节点插入链表的最后,需要增加一个指向链表最后一个尾节点的指针r。

    r->next=p;
    r=p;

    11:单链表的整表删除

      声明结点p和q;

      将第一个结点赋给p,下一节点赋值给q;

      循环执行释放p和将q赋值给p的操作;

     1 LinkList p,q;
     2 p= (*L)->next;
     3  while(p)
     4 {
     5   q=p->next;
     6   free(p);
     7   p=q;
     8 }
     9 (*L)->next=Null;
    10 return OK;

     单链表结构与顺序存储结构的优缺点:

    优缺点 顺序存储结构 单链表
    存储分配方式: 一段连续的存储单元依次存储线性表的数据元素 链式存储,用一组任意的存储单元存放线性表的元素
    时间性能: 查找O(1),插入和删除O(n) 查找O(n),插入和删除O(1)
    空间性能: 需要预先分配存储空间,较大浪费,较小溢出 利用零碎空间

     若线性表需要频繁查找,很少进行插入和删除操作,采用顺序存储结构合适。 例如用户注册信息,绝大多数都是读取数据。  

    若需要频繁的插入和删除,采用单链表合适。

    12:静态链表

    C语言的魅力在于指针的灵活性,使得它可以非常容易的操作内存中的地址和数据,比其他高级语言更加的灵活方便。(面向对象使用对象引用机制间接的实现了指针的某些功能。)

    用数组描述的链表称为静态链表。有两队元素:下标和游标。游标指向下一个元素的下标。游标类似于链表中的地址指针。数组的第一个和最后一个元素不存放数据,数组第一个游标存放数组第一个非空元素的下标,数组最后一个游标存放数组的第一个非空元素的下标。数组元素的最后一个非空元素的游标为0.同时,静态链表中的内存的获取与释放需要编程实现。

    13:循环链表

       单链表只是单向的,如果不从头结点出发,就无法访问到全部结点。

       将单链表中终端结点的指针端由空指针改为指向头节点,形成一个环,这种头尾相接的单链表为单循环链表。

      14:不用头指针,使用尾指针指向最后一个。

     

    判断单链表是否有环?

     

      双向链表

      双向链表的插入操作:(注意顺序)

    s->next=p;
    s->prior=p->prior;
    p->prior->next=s;
    p->prior=s;

    删除结点:

    p->prior->next=p->next;
    p->next->prior=p->prior;
    free(p);

    双向链表可以有效的提高算法的时间性能,用空间换取时间。

    #include <stdio.h>
    #include <stdlib.h>
    
    #define OK 1
    #define ERROR 0
    
    typedef char ElemType;
    typedef int  Status;
    
    typedef struct DualNode
    {
        ElemType data;
        struct DualNode* prior;
        struct DualNode* next;
    }DualNode, *DuLinkList;
    
    Status InitList(DuLinkList *L)
    {
        DualNode *p, *q;
        int i;
    
        *L = (DuLinkList)malloc(sizeof(DualNode));
        if (!(*L))
        {
            return ERROR;
        }
        (*L)->prior = (*L)->next = NULL;
        p = (*L);
    
        for (i = 0; i < 26; i++)
        {
            q = (DualNode*)malloc(sizeof(DualNode));
            if (!q)
            {
                return ERROR;
            }
            q->data = 'A' + i;
            q->prior = p;
            q->next = p->next;
            p->next = q;
    
            p = q;
        }
        p->next = (*L)->next;
        (*L)->next->prior = p;
        (*L)->prior = p->prior;
        return OK;
    }
    
    void Caesar(DuLinkList* L, int i)
    {
        if (i > 0)
        {
            do 
            {
                (*L) = (*L)->next;
            } while (--i);
        }
    
        if (i < 0)
        {
            do
            {
                (*L) = (*L)->prior;
            } while (++i);
        }
    }
    
    int main()
    {
        DuLinkList L;
        int i,n;
    
        InitList(&L);
        printf("请输入一个整数:");
        scanf_s("%d", &n);
        printf("
    ");
        Caesar(&L, n);
    
        for (i = 0; i < 26; i++)
        {
            L = L->next;
            printf("%c", (L)->data);
        }
        printf("
    ");
        return 0;
    }

     15:栈(Stack)和队列

    栈是一个后进先出的线性表,它要求只在表尾进行删除和插入操作。

    所谓的栈,其实就是一个特殊的线性表。表尾称为栈顶(Top),相应的表头称为栈底(Bottom)。

    栈的插入(Push),栈的删除(Pop).最开始栈中不包含任何数据,称为空栈,此时栈顶就是栈底,然后数据从栈顶进入,栈顶和栈底分离。数据出栈时从栈顶弹出,栈顶下移,整个栈的当前容量变小。

    入栈操作在栈顶进行,每次向栈中压入一个数据,top指针加1,直到栈满为止。

    出栈操作就是在栈顶取出数据,栈顶指针下移,栈的当前容量-1。

     逆波兰表达式:(没有括号)逆波兰表达式又叫做后缀表达式,是一种没有括号,并严格遵循“从左到右”运算的后缀式表达方法。

     它的优势在于只用两种简单操作,入栈和出栈就可以搞定任何普通表达式的运算。其运算方式如下:

    如果当前字符为变量或者为数字,则压栈,如果是运算符,则将栈顶两个元素弹出作相应运算,结果再入栈,最后当表达式扫描完后,栈里的就是结果。
    16:队列(queue)
    只允许在一端进行插入操作,而在另一端进行删除操作的线性表。与栈相反,队列是一种先进先出的线性表。实现一个队列同样需要顺序表或链表作为基础。
    队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
     
    队列既可以用链表实现,也可以用顺序表实现。但和栈相反,栈一般我们通过顺序表实现,而队列我们常通过链表实现,称为链队列。

     创建一个队列:首先在内存中创建一个头节点,然后将队列的头指针和尾指针都指向这个生成的头结点,此时为空队列。

    队列的顺序存储结构:假设一个队列有n个元素,则顺序存储的队列需要建立一个大于n的存储单元,并把队列的所有元素存储在数组的前n个单元,数组下标为0的一端是队头。

     如果队头指针可以移动,那么出队列复杂度就可以下降。但要解决假溢出的问题。循环队列,取模操作。

    17:递归和分治思想:(归去来兮,递归效率较低,能用迭代最好用迭代,递归是函数自己调用自身,每次调用都需要入栈等操作。)

    斐波那契数列的递归序列:

    #include <stdio.h>
    int Fib(int i);
    int main()
    {
        int i;
        printf_s("请输入一个大于零的整数
    ");
        scanf_s("%d", &i);
        for (; i >= 0; i--)
        {
            printf_s("%d ", Fib(i));
        }
        
        return 0;
    }
    
    int Fib(int i)
    {
        if (i < 2)
            return i == 0 ? 0 : 1;
        return Fib(i - 1) + Fib(i - 2);
    }

     递归定义至少有一个终止条件,函数不再调用自身,开始返回。

    递归和迭代的区别:迭代使用的是循环结构,递归使用的是选择结构。但大量的递归调用会建立函数的副本,会消耗大量的时间和内存,而迭代不需要这种付出。

    递归函数分为调用阶段回退阶段,递归的回退顺序是它调用顺序的逆序。

    是否可以理解为不断的中断,保存后不断调用自身,然后返回执行剩余。

    void print()
    {
        char a;
        scanf_s("%c", &a);
        if (a != '#') print();
        if (a != '#') printf_s("%c", a);
    }

     分治思想:采取各个击破,分而治之的原则。当一个问题规模较大且不容易求解时,可以考虑将其分为几个小的模块,逐一解决。采用分治思想处理问题时,其各个小模块通常具有与大问题相同的结构。

    八皇后问题回溯,也可以当作递归问题。

    18:字符串:"BoyFriend","KMP"

    字符串String:是由零个或多个字符串组成的有限序列。

    19:树(Tree):是n(n>0)个结点的有限集。当n=0时,称为空树,在任意一个非空树当中:

    1)有且仅有一个特定的称为根节点(Root)的结点;当n>1时,其余结点可以分为m(m>0)个互不相交的有限集T1,T2...,Tm,其中每一个集合本身又是一个树,并且称为根的子树(SubTree)。

    n=0,根节点是唯一的。不能交叉。

    2)结点拥有的子树数称为结点的度(Degree),树的度取决于树内各结点度的最大值。

    3)度为0 的结点称为叶节点(Leaf)或终端结点;度不为0 的结点称为分支结点或非终端结点,除跟结点外,分支结点也称为内部结点。

    4)结点的层次(Level)从根开始,根为第一层,根的孩子为第二层。树中结点的最大层次称为树的深度(Depth)或高度

    5)如果将树中结点的各子树看成从左到右,是有次序的,不能交换,则称该树为有序树,否则为无序树

    6)森林(Forest)是m(m>0)棵互不相交的树的集合。对树中的每个结点而言,其子树的集合即为森林。

    20:树的存储结构

    双亲表示法:假设以一组连续空间存储树的结点,同时在每个结点中,附设一个指示其双亲结点在数组中位置的元素。每个结点除了知道自己之外,还知道双亲结点的位置。

    孩子表示法:

    孩子双亲表示法:

    21:二叉树(Binary Tree)

    是n(n>=0)个结点的有限集合,该集合或者为空集(空二叉树),或者由一个根节点和两棵互不相交的,分别称为根节点的左子树和右子树的二叉树组成。

     

     

     

     

     

     

     

     

     
     
     
  • 相关阅读:
    CAD输出图至Word
    win7激活工具
    IP地址出现错误
    x%内存可用的问题解决
    第一次来到博客园
    ++x和x++
    标准输入流输出流以及错误流
    关于main函数的参数
    hdu1465 动态规划
    静态变量(static)的特点
  • 原文地址:https://www.cnblogs.com/lemonzhang/p/12247209.html
Copyright © 2011-2022 走看看