• 常见的基本数据结构——表


    表 ADT

    形如A1,A2,A3,.....,An这样的表。这个表的大小是n,大小为0的表为空表。

    对于除空表外的任何表,我们说A[i+1]后继A[i]并且A[i-1]前驱A[i]。表中的第一个元素A[1]不定义前驱,最后一个元素A[N]不定义后继。

    表ADT上面的操作:PrintList,MakeEmpty,Find,FindKth,Insert,Delete。

    表的简单数组

    对表的所有操作都可以通过使用数组来实现。需要对表的最大值进行预估,估计大了会严重的浪费空间。

    数组实现使得PrintList和MakeEmpty以线性时间执行,而FindKth花费常数时间,但是插入和删除的代价

    是昂贵的。插入需要将后面的元素进行向后移动,删除需要将后面的元素向前移动。平均来看,还是以线性时间执行。

    链表

    为了避免插入和删除的开销,我们允许使用不连续存储。

    链表由一系列不必再内存中相连的结构组成。每一个结构均包含有表元素和指向后继元素的指针。我们称之为Next指针。最后的Next指针指向NULL。

    链表上面的操作

    对于PrintList和Find只需间指针传递到该表的第一个元素,然后用一些Next指针传递即可,此时的时间是线性的。删除命令可以通过修改指

    针来实现,插入命令需要申请空间调用mallo函数,然后调整两次指针。

    下面是链表的相关操作的具体实现

    链表的类型声明

    struct Node;
    typedef struct Node *PtrToNode;
    typedef PtrToNode List;
    typedef PtrToNode Position;
    
    struct Node{
         ElementType Element;
         Position Next;
    };

     

    判断链表是否为空

    int
    IsEmpty(List L){
         return L->Next == NULL;
    }

     

    判断当前是否为链表的末尾位置

    int
    IsLast(Position P,List L){
         return P->Next == NULL;
    }

     

    Find查找函数

    Position
    Find(ElementType X, List L){
        Position p;
        P = L->Next;
        while(p != NULL && p->Element != X){
            p = p->Next;
        } 
         return p;
    }

     

    链表的删除操作

    void 
    Delete(ElementType X, List L){
        Position p, TepCell;
        p = FindPrevious(X);
        if(!IsLast(P, L)){
            TmpCell = p->Next;
            p->Next = TmpCell->Next;
            free(TmpCell);
        } 
    }
    
    Position
    FindPrevious(ElementType X, List L){
        Position P;
        P = L;
        while(P->Next != NULL && P->Next->Element != X){
            p=p->next;
        }
        return P;
    }
        

    链表的插入操作

    void
    Insert(ElementType X, List L, Position P){
        Position TmpCell;
        TmpCell = malloc(sizeof(Struct Node));
        if(TMpCell == NULL){
            FatalError(”out of space”);
        }
        TmpCell->Element = X;
        TmpCell->Next = P->Next;
        P->Next = TmpCell;
    }

    注意上述的方法中的参数,虽然有些参数在函数中没有使用,但是之所以这么做是因为别的实现方法可能会用上。

    对于上述的操作,最坏的情况是扫描整个表,平均来看运行时间是O(N),因为必须平均扫描半个表。

    常见的错误

    当指针为空时,指向的是非法空间,使用指针的属性或者操作时将会产生错误,无论何时只要你确定一个指向,就必须要保证该指针不是NULL。

    删除表的不正确的做法

    void
    DeleteList(List L){
        Position P, Tmp;
        P = L->Next;
        L->Next = NULL;
        while(P != NULL){
            Tmp = P->Next;
            free(P);
            P = Temp;
        }
    }

     

    双链表

    有时候以倒序扫描链表很方便,标准的实现方法却是无能为力了,然而解决办法很简单,就是在数据域附加上一个域,使它包含指向前一个单元的指针即可。其附加的开销是它增加了空间,同时插入

    和删除的开销增加了一倍,因为有更多的指针需要定位。另一方面,它简化了删除操作,不再使用指向前驱的的指针来访问关键字。

    循环链表

    让最后的元素反过来指向第一个元素是一种流行的做法,若有表头,则最后的元素就指向表头,并且它还可以是双向链表。

    多项式ADT

    我们用表来定义一种一元(具有非负次幂)多项式的的抽象数据类型。如果多项式的大部分系数非零,那么可以用一个简单的数组来存储这些系数。

    多项式ADT的数组实现类型声明

    typedef struct{
        int Coeffarray[MaxDegree + 1];
        int HighPower;
    }* Polynomial;
    
    //将多项式初始化为0的过程
    void
    ZeroPolynomial(Polynomial Poly){
        int i;
        for(i=0; i<=MaxDegree; i++){
            Poly->CoffArray[i] = 0;
        }
        Poly->HighPower = 0;
    }

    两个多项式相加

    void 
    AddPolynomial(const Polynomial Poly1, const Polynomial Poly2, Polynomial PolySum){
        int i;
        ZeroPolynomial(PolySum)
        PolySum->HighPower = Max(Poly1->HighPower, Poly2->HighPower);
        for(i=PolySum->HighPower; i>=0; i++){
           PolySum->CoeffArray[i] = Poly1->CoeffArray[i] + Poly2->CoeffArray[i];
        }
    }

    两个多项式相乘的过程

    void
    MultPolynomial(onst Polynomial Poly1, const Polynomial Poly2, Polynomial PolyProd){
      int i, j;
      ZeroPolynomial(PolyProd);
      PolyProd->HighPower = Poly1->HighPower + Poly2->HighPower;
      if(PolyProd->HighPower > MaxDegree){
        Error(Except array size);
      }else{
        for(i=0; i<=Poly1->HighPower; i++){
          for(j=0; j<=Poly2->HighPower; j++){
            PolyPord->CoeffArray[i + j] = Poly1->CoeffArray[i] * Poly2->CoeffArray[j];
          }
        }
      }
    }

    多项式的另一种表示方法:

    通过单链表的方式表示多项式,多项式的每一项保存在链表元素中,并且按照幂的大小间须降序进行排列。

    多项式的表示多用于较稀疏的多项式的情况,需要注意的操作时,当链表的多项式相乘时,需要进行多项式的合并同类型。

    typedef struct Node *PtrToNode;
    struct Node{
      int Coefficient;
      int Expoent;
      PtrToNode Next;
    };
    typedef PtrToNode Polynomial;

     

    基数排序

    将链表用于基数排序(radix sort),基数排序有时也称为卡式排序,因为在现代计算机出现之前,它一直用于老式穿孔卡的排序。

    如果我们有N个整数,它的范围是从1到M(或者是0到M-1),我们可以利用这个信息得到一种快速排序,叫做桶式排序。我们留置一个数组称为Count,大小为M,并初始化为0。那么,Count有M个单元(桶),开始时他们都是空的。当Ai被读入时令Count[Ai]+1。当所有的输入结束后,扫描Count数组,打印好排序的数组。该算法花费的时间是O(M+N),如果M=N,那么桶排序为O(N)。

    基数排序是这种方法的推广。下面的这个例子就是详细的说明,设我们有10个数,范围是0到999之间,我对其排序。一般来说,这是0到N^P-1之间的N个数,p是某个常数。显然我们不适合使用桶排序,因为这样桶太多了。于是我们的策略是使用多次桶排序,通过用最低位优先的方式,进行桶排序,算法将得到正确的结果。如果使用最高位将会出现错误,而且还无法判断最高位。当然,有时会有多个数落入到桶中,而且这些数还是不同的,因此我们需要使用一个表来保存,因为所有的数字都可能有某位,所以用简单数组来保存的话,数组的空间需求是O(N^2)。

    下面是10个数的桶式排序的具体例子。本例的输入是:64,8,216,512,27,729,0,1,343,64。为了是问题简化,此时操作按基是10进行,第一遍桶排序的结果是0, 1, 512, 343, 64, 125, 216, 27, 8, 729。在使用次低位对第一遍桶排序的结果进行排序,得到第二次桶排序的结果:0,1,8,512,216,125,27,729,343,64。最后按照最高位进行排序,最后得到的表:0,1,8,27,64,125,216,343,512,729。

    第一趟桶排序结果

    0

    1

    512

    343

    64

    125

    316

    27

    8

    729

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    第二趟桶排序结果

    8

    1

    0

    216

    512

    729

    27

    125

    343

    64

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    第三趟桶排序结果

    67

    27

    8

    1

    0

    125

    216

    343

    512

    729

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    在使用数组进行实现时,下面的算法的步骤:

      1.计算待排序数组的最大位数。

      2.对桶数组和下表数组进行初始化,根据位数进行循环。

      3.根据当前的位数,对待排数组进行遍历,计算出当前位的数值,根据数值将其放入对应的桶数组中,并更新下表数组。

      4.将桶数组中的元素更新到待排数组中,

      5.更新位数计算,跳转至2步骤,直至位数大于最大位数。

    该排序算法的时间复杂度是O(P(N+B)),其中P是排序趟数,N是要被排序的元素的个数,B是桶数。该算法的一大缺点是不能用于浮点数的排序。

    当我们把32位机器所能便是的所有整数进行排序时,假设我们在大小为2^11的桶分三趟进行即可,并且算法总是O(N)的时间消耗。

    多重表

    常见的多重链表是十字链表,十字链表的特点是对于二维的数据,它也能够通过不连续的空间进行表达,即水平方向和垂直方向都是都是链表,且可以是循环链表。

    链表的游标实现

    在一些不支持指针的语言中,如果需要链表而不能使用指针的话,那么游标法是一种实现方式。

    链表的指针实现的重要特点

    1.数据存储在一组结构体中,每个结构体含有指向下一个结构体的指针。

    2.一个新的结构体可以通过调用malloc从系统中得到,并通过调用free而被释放。

    游标实现必须要满足上述条件,条件一可以通过全局结构体数组实现,通过数组下标代表一个地址。

    下面是游标的实现

    struct Node{
      ElementType Element;
      Position Next;
    }
    struct Node CursoSpace[SpaceSize];

    为了模拟条件二,让CursorSpace数组中的单元代替malloc和free的职能。为此,我们保留一个表,表由不再任何表中的元素构成。该表将用0作为表头,对于Next,0的值为NULL,为了执行malloc功能,将第一个元素从freelist中删除,为了执行free功能,我们将该单元放在freelist的前段。

    下面是malloc函数和free函数的实现

    static Position
    CoursorAlloc(void){
      Position P;
      P = CoursorSpace[0].next;
      CursirSpace[0].Next = CursorSpace[P].next;
      return P;
    }
    static void
    CursorFree(Position P){
      CursorSpace[P].next = CursorSpace[0].next;
      CursorSpace[0].next = P;
    }

    下面是一个链表游标实现的列表:

    slot

    Element

    Next

    0

    -

    6

    1

    b

    9

    2

    f

    0

    3

    header

    7

    4

    -

    0

    5

    header

    10

    6

    -

    4

    7

    c

    8

    8

    d

    2

    9

    e

    0

    10

    a

    1

    slot

    Element

    Next

    0

    -

    6

    1

    b

    9

    2

    f

    0

    3

    header

    7

    4

    -

    0

    5

    header

    10

    6

    -

    4

    7

    c

    8

    8

    d

    2

    9

    e

    0

    10

    a

    1

    判断链表是否为空
    int IsEmpty(List L){
      return CursorSpace[L].Next == 0;
    }
    判断P是否是链表的末尾
    int IsLast(Position P, List L){
      return CursorSpace[P].Next == 0; 
    }
    
    游标的Find实现
    Position Find(Element X, List L){
      Position P;
      P = CursorSpace[L].Next;
      while(P && CursorSpace[P].Element != X){
        P = CursorSpace[P].Next;
      } 
      reutrn P;
    }
    
    链表的delete操作
    void Delete(Element X, List L){   Position P, TmpCell;   P = FindPrevious(X, L);   if(!IsLast){     TmpCell = CursorSpace[P].Next;     CursorSpace[P].Next = CursorSpace[TmpCell].Next;     CursorFree(TmpCell);   } }
  • 相关阅读:
    嵌入式硬件设计时所需考虑的几个问题
    MySQL网络配置
    MySQL数据库操作技术大全
    关于硬件芯片未用引脚的处理方法
    与嵌入式软件开发相关的一些硬件知识
    C语言-联合(union)
    AtCoder Regular Contest 101 D
    AtCoder Regular Contest 101 C
    AtCoder Regular Contest 102 C
    线性基学习
  • 原文地址:https://www.cnblogs.com/baby-lily/p/12194123.html
Copyright © 2020-2023  润新知