期末考试

数据结构期末


第一章 绪论

数据结构的定义:是一门研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作等的学科。

\[ 结构\begin{cases} 存储结构 \begin{cases} 顺序存储结构:借助元素之间相对位置来表示数据元素之间的逻辑关系\\ 链式存储结构:借助指示元素存储地址的指针表示数据元素之间的逻辑关系\\ \end{cases}\\ 逻辑结构 \begin{cases} 集合结构\\ 线性结构\\ 树形结构\\ 图状结构\\ \end{cases}\\ \end{cases} \]

数据项:一个数据元素可由若干个数据项组成,数据项是数据不可分割的最小单位。

四类基本的数据结构(逻辑结构):集合,线性表,树和图

数据类型:是一组性质相同的值的集合以及定义与这个值的集合上的一组操作的总称

算法的五个特性:有穷性,确定性,可行性,0个或多个输入,一个或多个输出

算法的设计要求:正确性,可读性,健壮性,高效率,低存储

算法的时间复杂度:

  • 常数阶O(1)
  • 对数阶\(O(log_n)\)
  • 线性阶\(O(n)\)
  • 线性对数阶\(O(nlog_n)\)
  • 平方阶\(O(n^2)\)
  • 立方阶\(O(n^3)\)
  • 指数阶\(O(2^n)\)
  • 阶层阶\(O(n!)\)

算法的空间复杂度:算法在运行过程中临时所占用的空间

第2章 线性表

  1. 线性表的定义:一个线性表是n个数据元素的有限序列。

  2. 线性表的特点:在数据元素的非空有限集中

    • 存在惟一的一个被称作“第一个”的数据元素
    • 存在惟一的一个被称作“最后一个”的数据元素
    • 除第一个数据元素之外,集合中的每一个元素均只有一个前驱
    • 除最后一个数据元素外,集合中每一个数据元素均只有一个后继
  3. 加工型操作和引用型操作的区别

  4. 线性表的顺序表示和实现

    1. 线性表的动态顺序存储表示

      //存储结构
      #define LIST_INIT_SIZE 100      //初始化容量
      #define LISTINCREAMENT 10       //扩充增量
      typedef int Status;
      
      typedef struct{
          ElemType *elem;
          int length;
          int listsize;
      }SqList;
      
      //初始化
      Status InitList(SqList * L)
      {
          L->elem = (ElemType *)malloc(LIST_INIT_SIZE * sizeof(ElemType));
          if(!L->elem)
          {
              exit(OVERFLOW);
          }
      
          L->length = 0;
          L->listsize = LIST_INIT_SIZE;
      
          return OK;
      }
      
      //插入
      Status ListInsert_Sq(SqList *L, int pos, ElemType e)
      {
          ElemType *newbase = NULL;
          int i;
      
          //1.判断插入位置是否合法
          if(pos < 1 || pos > L->length + 1)
          {
              return ERROR;
          }
          //2.判断空间是否足够
          if(L->length >= L->listsize)
          {
              newbase = (ElemType *)realloc(L->elem,(L->listlize + LISTINCREAMENT)*sizeof(ElemType));
      
              if(!newbase)
              {
                  exit(OVERFLOW);
              }
      
              L->elem = newbase;
              L->listsize += LISTINCREAMENT;
          }
          //3.元素后移
          for(i = L->length-1; i >= pos-1; --i)
          {
              L->elem[i+1] = L->elem[i];
          }
          //4.完成插入
          L->elem[pos-1] = e;
          ++(L->length);
      
          return OK;
      }
      
      //删除元素
      Status ListDelete_Sq(SqList *L, int pos, ElemType *e)
      {
          int i;
      
          //1.判断删除位置是否合法
          if(pos < 1 || pos > L->length)
          {
              return ERROR;
          }
          //2.移动元素
          (*e) = L->elem[pos-1];
      
          for(i = L->length-1; i > pos-1; --i)
          {
              L->elem[i-1] = L->elem[i];
          }
          //3.完成删除
          --L->length;
          return OK;
      }

      注:

      1. 删除一个节点平均移动元素的个数 = \(\frac{n-1}{2}\),删除第一个移动n-1个元素,最后一个不移动
      2. 插入一个节点平均移动的元素的个数 = \(\frac{n}{2}\),第一号位置插入,移动n个,最后一个位置插入不移动
    2. 线性表的链式存储表示

      //存储结构
      typedef struct Node{     //需要使用struct Node,故需要起名字
          ElemType data;
          struct Node *next;
      }LNode, *LinkList;
      
      //获得指定位置的元素
      Status GetElem(LinkList L, int pos, ElemType *e)
      {
          LinkList p = L->next;
          int j = 1;
      
          while(p && j < pos)
          {
              p = p->next;
              ++j;
          }
      
          //p为null表示pos大于表长,j > pos,表示pos < 1
          if(!p || j > pos)
          {
              return ERROR;
          }
      
          (*e) = p->data;
          return OK;
      }
      
      //插入元素
      Status ListInsert_L(LinkList *L, int pos, ElemType e)
      {
          LinkList p = (*L);
          LinkList q = NULL;
          int j = 0;
      
          //1.寻找第pos个元素pos-1
          while(p && j < pos-1)
          {
              p = p->next;
              ++j;
          }
      
          //2.判断位置是否合法
          if(!p || j > pos-1)
          {
              return ERROR;
          }
      
          //3.生成新节点,并插入
          q = (LinkList)malloc(sizeof(LNode));
          q->data = e;
          q->next = p->next;
          p->next = q;
      
          return OK;    
      }
      
      //删除元素
      Status ListDelete_L(LinkList *L, int pos, ElemType *e)
      {
          LinkList p = (*L);
          LinkList q = NULL;
          int j = 0;
      
          //1.寻找第pos-1个元素
          /*
          注意:为防止删除的元素是最后一个元素的下一个元素,导致free(NULL)导致程序崩溃,所以此处使用p->next进行判断,使p永远不会指向最后一个元素。
          */
          while(p->next && j < pos-1)
          {
              p = p->next;
              ++j;
          }
      
          //2.判断位置是否合法
          if(!(p->next) || j > pos-1)
          {
              return ERROR;
          }
      
          //3.删除
          (*e) = p->next->data;
          q = p->next;
          p->next = q->next;
          free(q);
      
          return OK;
      }
      
      //链表的创建
      Status CreateList(LinkList *L, int n)
      {
          int i = 0;
          LinkList p = NULL;
      
          //1.创建头结点
          (*L) = (LinkList)malloc(sizeof(LNode));
          if(!(*L))
          {
              return ERROR;
          }
          (*L)->next = NULL;
      
          //2.创建单链表
          for(i = 0; i < n; ++i)
          {
              p = (LinkList)malloc(sizeof(LNode));
              if(!P)
              {
                  return ERROR;
              }
      
              scanf("%d", &p->data);
      
              p->next = (*L)->next;
              (*L)->next = p;
          }
      
          return OK;
      }

      注意:

      • 时间复杂度与元素插入删除的位置有关,故时间复杂度为\(O(n)\) ,需要找出前驱
    3. 静态链表

      • 使用数组(结构体数组)模拟的链表,使用游标cur表示节点的指针域

        #define MAXSIZE 1000
        //存储结构
        typedef struct{
            ElemType data;
            int cur;
        }component, SLinkList[MAXSIZE];

        注:

        • 使用游标将所有元素连在一起构成备用链表,备用链表设置头结点,一般以0号元素作为备用链表的头结点。
    4. 循环链表,最后一个元素的指针域指向头结点,不存在空指针域,并且为了操作方便常常设置尾指针来标志一个双向循环链表,判断遍历结束的标志是next域是否等于头指针

    5. 双向循环链表

      //存储结构
      typedef struct DuLNode{
          ElemType data;
          struct DuLNode* prior;
          struct DuLNode* next;
      }DuLNode, *DuLinkList;
      
      //创建双向循环链表
      Status CreateDuLinkList(DuLinkList *DL)
      {
          (*DL) = (DuLinkList)malloc(sizeof(DuLNode));
      
          if(!(*DL))
          {
              return ERROR;
          }
      
          (*DL)->next = (*DL);
          (*DL)->prior = (*DL);
          return OK;
      }
      
      //插入,在第pos个元素位置之前插入
      Status ListInsert(DuLinkList *DL, int pos, ElemType e)
      {
          DuLinkList p = NULL, s = NULL;
          //1.找位置
          if(!(p = GetElemP_DuL(DL, pos)))
          {
              return ERROR;
          }
          //2.开空间
          if(!(s = (DuLinkList)malloc(sizeof(DuLNode))))
          {
              return ERROR;
          }
          //3.插入
          s->data = e;
          s->next = p;
          s->prior = p->prior;
          p->prior->next = s;
          p->prior = s;
          return OK;
      }
      
      //删除
      Status ListDelete_DuL(DuLinkList *DL, int pos, ElemType* e)
      {
          if(!(p = GetElemP_Dul))
          {
              return ERROR;
          }
      
          (*e) = p->data;
          p->prior->next = p->next;
          p->next->prior = p->prior;
          return OK;
      }

      注意:

      • 双向循环链表在插入和删除元素的时候不需要找前驱,因为每一个元素均可以直接找到自己的前驱和后继。

猜你喜欢

转载自www.cnblogs.com/zcxhaha/p/10200224.html