1. 程式人生 > 其它 >python之內建abs()函式詳解

python之內建abs()函式詳解

本篇內容是根據B站郝斌資料結構的學習筆記,本篇筆記是用markdown根據Typora編寫,copy到部落格有些格式微調,如發現錯誤望留言指正,不勝感激!

如有侵權請聯絡我刪除:[email protected]

如想轉載請註明出處謝謝!

ps:本篇篇幅較長,請慢慢享用!

 

資料結構

概述

  • 資料結構定義

    我們如何把現實中大量而複雜的問題以特定的資料型別和特定的儲存結構儲存到主儲存器(記憶體)中,以及在此基礎上為實現某個功能(比如查詢某個元素,刪除某個元素,對元素進行排序等)而執行的相應操作,這個相應的操作也叫演算法。

    資料結構 = 個體的儲存 + 個體的關係儲存

    演算法 = 對儲存資料的操作

  • 演算法定義

    • 通俗的說演算法是解題的方法和步驟

    • 衡量演算法的標準

      1. 時間複雜度:程式大概要執行的次數,而非執行的時間。

      2. 空間複雜度:程式執行過程中大概所佔用的最大記憶體空間。

      3. 難易程度:用易懂,避免過於複雜。

      4. 健壯性

  • 資料結構的地位

    資料結構是軟體中最核心的課程

    程式=資料的儲存+資料的操作+可以被計算機執行的語言

預備知識

  1. 指標

    • 指標的重要性:指標是C語言的靈魂

    • 定義:

      • 地址:記憶體單元的編號,從0開始的非負整數

      • 指標:指標就是地址,地址就是指標;指標變數是存放記憶體單元地址的變數;指標的本質是一個操作受限的非負整數。

    • 分類:基本型別的指標;指標和陣列的關係

  2. 結構體

    • 為什麼會出現結構體:為了表示一些複雜的資料,而普通的基本型別變數無法滿足要求;

    • 定義:結構體是使用者根據實際需要自己定義的複合數型別;

    • 如何使用結構體

      //定義結構體
      struct Student
      {
          int sid;
          char name[200];
          int age;
      };
      • //整體賦值,類似於Java中new類的建構函式 
        struct Student st = {1001,"zhangsan",18};
        //單個賦值
        st.id=1001;
        strcpy(st.name,"zhangsan");
        st.age
        =18; 
      • //通常使用指標的方式賦值
        struct Student *pst;
        //pst所指向的結構體變數中的sid這個成員
        pst->sid=1001;
        strcpy(pst->name,"lisi");
        pst->age=19
         
    • 注意事項:結構體變數不能算術計算,但是可以賦值;普通結構體變數和結構體指標變數作為函式傳參的問題,推薦使用傳遞結構體指標的方式,這樣效率高節約記憶體。

  3. 動態記憶體的分配和釋放   

 樣例程式碼:    

 1  1 一維陣列:
 2  2 
 3  3 # include <stdio.h>
 4  4 # include <malloc.h>
 5  5 6  6 int main(void)
 7  7 {
 8  8  //靜態分配陣列
 9  9  int a[5] = {2,3,5,6,9};
10 10  //len為一維陣列的長度,可以根據需求動態分配長度
11 11  int len;
12 12  printf("請輸入你需要分配的陣列長度:len=");
13 13  scanf("%d",&len);//len=5
14 14  //malloc為分配記憶體的函式,返回第一個位元組的地址,但是預設返回是一個乾地址,沒有實際意義,必須加強制型別轉換為指定的指標型別才有意義,(int *)表示強轉為int型別的指標,那麼返回的地址指向的就是第一個元素的地址,那麼第二個元素的地址就是第一個地址向後挪一位
15 15  int * pArr = (int *)malloc(sizeof(int) * len);
16 16  *pArr = 2;//類似於 a[0]=4,因為陣列名就是指向了第一個元素的地址,跟*pArr一樣
17 17  pArr[1] = 3; //類似於 a[1]=3
18 18  
19 19  free(pArr);//把pArr所代表的動態分配的20個位元組的記憶體釋放
20 20  
21 21  return 0;
22 22 }
  1. 跨函式使用記憶體

     1 # include <stdio.h>
     2 # include <malloc.h>
     3  4 struct Student
     5 {
     6  int sid;
     7  int age;
     8 };
     9 10 struct Student * CreateStudent(void);
    11 void ShowStudent(struct Student *);
    12 13 int main(void)
    14 {
    15     struct Student * ps;
    16     ps = CreateStudent();
    17  ShowStudent(ps);
    18  return 0;
    19 }
    20 21 void ShowStudent(struct Student * pst)
    22 {
    23  printf("%d %d",pst->sid,pst->age);
    24 }
    25 26 struct Student * CreateStudent(void)
    27 {
    28     struct Student * p = (struct Student *)malloc(sizeof(struct Student));
    29  p->sid = 1001;
    30  p->age = 18;
    31  return p;
    32 }
  2. typedef函式的使用

     1 typedef int INT; // 相當於給int起了一個別名 INT
     2 typedef struct Student
     3 {
     4  int sid;
     5  char name[100];
     6  char sex;
     7 } ST; //ST st 就相當於 struct Student st,給struct Student 起了別名ST,這樣簡潔了程式碼
     8 typedef struct Student
     9 {
    10  int sid;
    11  char name[100];
    12  char sex;
    13 } * ST; //ST就相當於struct Student *  

線性結構

把所有節點用一條直線串起來

  • 連續儲存【陣列】

    1. 什麼叫陣列:元素型別相同,大小相等

    2. 陣列的優缺點

      • 優點:存取速度很快

      • 缺點:插入刪除元素很慢

      陣列例子

        1 # include <stdio.h>
        2 # include <malloc.h>
        3 # include <stdlib.h>
        4 # include <stdbool.sh>
        5   6 struct Arr
        7 {
        8     int * pBase;//陣列首地址
        9     int cnt;//當前陣列內已有元素長度
       10     int len;//陣列長度
       11 };
       12  13 //初始化陣列
       14 void init_arr(struct Arr * pArr,int length);
       15 //獲取某個位置上的元素
       16 int get(int pos);
       17 //判斷陣列是否已經滿了
       18 bool is_full(struct Arr * pArr);
       19 //判斷陣列是否為空
       20 bool is_empty(struct Arr * pArr);
       21 //列印陣列內所有元素
       22 void show_arr(struct Arr * pArr);
       23 //向陣列中追加元素
       24 void append_arr(struct Arr * pArr,int val);
       25 //向陣列某個位置插入元素
       26 void insert_arr(struct Arr * pArr,int pos,int val);
       27 //排序
       28 void sort_arr (struct Arr * pArr);
       29  30 int main(void)
       31 {
       32     struct Arr arr;
       33     init_arr(&arr,6);
       34     
       35     
       36     return 0;
       37 }
       38  39 void init_arr(struct Arr * pArr,int length)
       40 {
       41     pArr->pBase = (int *)malloc(sizeof(struct Arr) * length);
       42     if ( NULL == pArr->pBase)
       43     {
       44         printf("動態記憶體分配失敗\n");
       45         exit(-1);
       46     }
       47     else 
       48     {
       49         pArr->len = length;
       50         pArr->cnt = 0;
       51     }
       52     return;
       53 }
       54  55 bool is_empty(struct Arr *pArr)
       56 {
       57     return 0 == pArr->cnt ? true:false;
       58 }
       59  60 bool is_full(struct Arr * pArr)
       61 {
       62     return pArr->cnt == pArr->len ? true:false;
       63 }
       64  65 void show_arr(struct Arr * pArr)
       66 {
       67     int i;
       68     if ( is_empty(pArr) )
       69     {
       70         printf("陣列為空");
       71     }
       72     else 
       73     {
       74         for (i = 0;i<pArr->cnt;i++)
       75         {
       76             printf("%d ",pArr->pBase[i]);
       77         }
       78         printf("\n");
       79     }
       80     return;
       81 }
       82  83 void append_arr(struct Arr * pArr,int val)
       84 {
       85     if( is_full(pArr) )
       86     {
       87         printf("陣列已滿,不能再追加!\n");
       88     }
       89     else 
       90     {
       91         pArr->pBase[pArr->cnt]=val;
       92         ++ pArr->cnt;
       93     }
       94     return;
       95 }
       96  97 void insert_arr(struct Arr * pArr,int pos,int val)
       98 {
       99     if ( is_full(pArr) )
      100     {
      101         printf("陣列已滿,無法插入!\n");
      102         return;
      103     }
      104     if ( pos < 1 || pos > pArr->cnt)
      105     {
      106         printf("指定位置有誤,無法插入!\n");
      107         return;
      108     }
      109     
      110     for (int i=pArr->cnt; i<pos-1; --i)
      111     {
      112         pArr->pBase[i+1]=pArr->pBase[i];
      113     }
      114     pArr->pBase[pos-1]=val;
      115     pArr->cnt ++;
      116     return;
      117 }
      118 119 void inversion_arr (struct Arr * pArr)
      120 {
      121 int i;
      122 int j = pArr-> cnt -1;
      123 int t;
      124 
      125 while (i < j) 
      126     {
      127 t = pArr->pBase[i];
      128 pArr->pBase[i] = pArr->pBase[j];
      129 pArr->pBase[j] = t;
      130 ++i;
      131 --j;
      132     }
      133 return;
      134 }
      135 136 void sort_arr (struct Arr * pArr)
      137 {
      138 int i,j,t;
      139 140 for (i=0; i<pArr->cnt-1; ++i)
      141     {
      142 for (j=i+1; j<pArr->cnt; ++j)
      143     {
      144 if (pArr->pBase[i]>pArr->pBase[j])
      145     {
      146 t = pArr->pBase[j];
      147 pArr->pBase[j]=pArr->pBase[i];
      148 pArr->pBase[i]=t;
      149     }
      150     }
      151     }
      152 return;
      153 }
  • 離散結構【連結串列】

    • 定義:n個節點離散分配,彼此通過指標相連,每個節點只有一個前驅節點同時每個節點只有一個後續節點,首節點沒有前驅節點,尾節點沒有後續節點。

      專業術語

      • 首節點:存放第一個有效資料的節點

      • 尾節點:存放最後一個有效資料的節點

      • 頭結點:位於首節點之前的一個節點,頭結點並不存放有效的資料,加頭結點的目的主要是為了方便對連結串列的操作

      • 頭指標:指向頭結點的指標變數

      • 尾指標:指向尾節點的指標變數

    • 確定一個連結串列需要幾個引數:只需要一個頭指標引數,因為我們通過頭指標可以推算出連結串列的其他所有資訊

      例子

       1 # include <stdio.h>
       2  3 typedef struct Node
       4 {
       5  int data;//資料域
       6  struct Node * PNext;//指標域
       7 } NODE, *PNODE;//NODE等價於struct Node,PNODE等價於struct Node *
       8  9 int main(void)
      10 {   
      11  return 0;
      12 }
    • 分類

      • 單鏈表:每一個節點只有一個指標域

      • 雙鏈表:每一個節點有兩個指標域

      • 迴圈連結串列:能通過任何一個節點找到其他所有的節點

      • 非迴圈連結串列:不能通過任何一個節點找到其他所有的節點

        迴圈連結串列屬於雙鏈表的一種特殊形式,即迴圈連結串列是雙鏈表的一個子集。

    • 優缺點

      • 優點:空間沒有限制,插入和刪除元素很快

      • 缺點:存取速度很慢

    • 演算法

      演算法:

      • 俠義的演算法是與資料的儲存方式密切相關

      • 廣義的演算法是與資料的儲存方式無關

      泛型:利用某種技術達到的效果就是:不同的儲存方式,執行的操作是一樣的

      • 遍歷

      • 查詢

      • 清空

      • 銷燬

      • 求長度

      • 排序

      • 刪除節點

      • 插入節點

      程式碼實現

        1 # include <stdio.h>
        2 # include <malloc.h>
        3 # include <stdbool.h>
        4   5 typedef struct Node
        6 {
        7 int data;//資料域
        8 struct Node * pNext;//指標域
        9 }NODE,*PNODE;//NODE相當於struct Node,*PNODE相當於struct Node *
       10  11 //建立連結串列
       12 PNODE create_list(void);
       13 //遍歷連結串列
       14 void traverse_list(PNODE pHead);
       15 //判斷是否為空
       16 bool is_empty(PNODE pHead);
       17 //返回連結串列長度
       18 int length_list(PNODE pHead);
       19 //在指定節點處插入某個元素
       20 bool insert_list(PNODE,int,int);
       21 //刪除指定位置的元素
       22 bool delete_list(PNODE,int,int *);
       23 //對連結串列排序
       24 void sort_list(PNODE pHead);
       25  26 int main(void)
       27 {
       28 PNODE pHead = NULL;//定義頭結點指標
       29 pHead = create_list();
       30 return 0;
       31 }
       32  33 PNODE create_list(void)
       34 {
       35 int len;//連結串列成員個數,由使用者輸入
       36 int i;
       37 int val;//連結串列成員值,由使用者輸入
       38  39 PNODE pHead = (PNODE)malloc(sizeof(NODE));//定義頭結點指標
       40 if (NULL == pHead)
       41 {
       42   printf("分配記憶體失敗,程式結束");
       43   exit(-1);
       44 }
       45  46 printf("請輸入連結串列長度,len=");
       47 scanf("%d",&len);
       48  49 PNODE pTail = pHead;
       50 pTail->pNext = NULL;
       51  52 for(i=0; i < len; i++)
       53 {
       54   PNODE pNew = (PNODE)malloc(sizeof(NODE));
       55   if (NULL == pNew)
       56      {
       57     printf("分配記憶體失敗,程式結束");
       58     exit(-1);
       59      }
       60   printf("請輸入要插入連結串列的值,val=");
       61      scanf("%d",&val);
       62   
       63   pNew->data = val;
       64   pTail->pNext = pNew;
       65   pNew->pNext = NULL;
       66   pTail = pNew;
       67 }
       68 return pHead;
       69 }
       70  71 void traverse_list(PNODE pHead)
       72 {
       73  PNODE p = pHead->pNext;
       74  if (NULL != p)
       75  {
       76      printf("%d\t",p->data);
       77      p = p->pNext;
       78  }
       79  printf("\n");
       80  return;
       81 }
       82  83 bool is_empty(PNODE pHead)
       84 {
       85  if (NULL == pHead->pNext)
       86     return true;
       87  else
       88      return false;
       89 }
       90  91 int length_list(PNODE pHead)
       92 {
       93  int len = 0;
       94  PNODE p = pHead->pNext;
       95  while(NULL != p)
       96  {
       97      ++len;
       98      p = p->pNext;
       99  }
      100  return len;
      101 }
      102 103 bool insert_list(PNODE pHead,int pos,int val)
      104 {
      105  int i;
      106  PNODE p = pHead;
      107  //迴圈到p指向pos-1的位置
      108  while( NULL != p && i<pos-1)
      109  {
      110      p = p->pNext;
      111      ++i;
      112  }
      113  if (NULL == p || i > pos -1)
      114  {
      115      return false;
      116  }
      117  //插入的數申請記憶體
      118  PNODE pNew = (PNODE)malloc(sizeof(NODE));
      119  if (NULL == pNew)
      120     {
      121         printf("分配記憶體失敗,程式終止!\n");
      122         exit(-1);
      123     }
      124  pNew->data = val;
      125  PNODE q = p->pNext;
      126  p->pNext = pNew;
      127  pNew->pNext = q;
      128  return ture;
      129 } 
      130 131 bool delete_list(PNODE pHead,int pos,int *pVal)
      132 {
      133  int i;
      134  PNODE p = pHead;
      135  //迴圈到p指向pos-1的位置
      136  while( NULL != p->pNext && i<pos-1)
      137  {
      138      p = p->pNext;
      139      ++i;
      140  }
      141 if (NULL == p->pNext || i > pos -1)
      142  {
      143      return false;
      144  }
      145 PNODE q = p->pNext;
      146 *pVal = p->data;
      147 p->pNext=q->pNext;
      148 free(q);
      149 q=NULL;
      150 return true;
      151 }
      152 153 void sort_list(PNODE pHead)
      154 {
      155 int i,j,t;
      156 PNODE p,q;
      157 int len = length_list(pHead);
      158 for(i=0,p=pHead->pNext;i<len-1;i++,p=p->pNext)
      159  {
      160      for(j=i+1,q=p->pNext;j<len;j++,q=q->pNext)
      161      {
      162          if(p->data > q->data)
      163          {
      164              t = p->data;
      165              p->data = q->data;
      166              q->data = t; 
      167          }
      168      }
      169  }
      170 return;
      171

       

  • 線性結構的兩種常見應用之一:棧

    • 定義:一種可以實現“先進後出”的儲存結構

    • 分類:靜態棧;動態棧。

    • 演算法:壓棧;出棧

    • 應用:函式呼叫;中斷;表示式求值;記憶體分配;緩衝處理;迷宮

    示例程式碼

      1 # include <stdio.h>
      2 # include <malloc.h>
      3 # include <stdlib.h>
      4   5 typedef struct Node
      6 {
      7     int data;
      8     struct Node * pNext;
      9 }NODE,* PNODE;
     10  11 typedef struct Stack
     12 {
     13     PNODE pTop;
     14     PNODE pBottom;
     15 }STACK,* PSTACK;
     16  17 //初始化
     18 void init_stack(PSTACK);
     19 //壓棧
     20 void push_stack(PSTACK,int);
     21 //出棧
     22 int pop_stack(PSTACK);
     23 //遍歷棧
     24 void traverse_stack(PSTACK);
     25 //判斷棧是否為空
     26 bool is_empty(PSTACK);
     27 //清空棧
     28 void clear(PSTACK);
     29  30 int main(void)
     31 {
     32     int val;//出棧的值
     33     STACK s;
     34     init_stack(&s);//目的是造出一個空棧
     35     push_stack(&s,1);//壓棧
     36     push_stack(&s,2);
     37     push_stack(&s,3);
     38     push_stack(&s,4);
     39     push_stack(&s,5);
     40     traverse_stack(&s);
     41     pop_stack(&s,&val);
     42     return 0;
     43 }
     44  45 void init_stack(PSTACK pS)
     46 {
     47     pS->pTop=(PNODE)malloc(sizeof(NODE));
     48     if (NULL == pS->pTop)
     49     {
     50         printf("分配記憶體失敗,程式終止!\n");
     51         exit(-1);
     52     }
     53     else 
     54     {
     55         pS->pBottom = pS->pTop;
     56         pS->pTop->pNext = NULL;
     57     }
     58     return;
     59 }
     60  61 void push_stack(PSTACK pS,int val)
     62 {
     63     PNODE pNew = (PNODE)malloc(sizeof(NODE));
     64     if (NULL == pNew)
     65     {
     66         printf("分配記憶體失敗,程式終止!\n");
     67         exit(-1);
     68     }
     69     pNew->data = val;
     70     pNew->pNext = pS->pTop;
     71     pS->pTop = pNew;
     72     
     73     return;
     74 }
     75  76 void traverse_stack(PSTACK pS)
     77 {
     78     
     79     PNODE p = pS->pTop;
     80     while (p != ps->pBottom) 
     81     {
     82         printf("%d\t",p->data);
     83         p = p->pNext;
     84     }
     85     printf("\n");
     86     return;
     87 }
     88  89 bool is_empty(PSTACK pS)
     90 {
     91     if (pS->pTop == pS->pBottom)
     92         return true;
     93     else 
     94         return false;
     95 }
     96  97 bool pop_stack(PSTACK pS,*pVal)
     98 {
     99     if (is_empty(pS)){
    100         return false;
    101     }
    102     else 
    103     {
    104         PNODE p = pS->pTop;
    105         *pVal = p->data;
    106         pS->pTop = p->pNext;
    107         free(p);
    108         p = NULL;
    109         return true;
    110     }
    111 }
    112 113 void clear(PSTACK pS)
    114 {
    115     if(is_empty(pS))
    116         return;
    117     else 
    118     {
    119         PNODE p = pS->pTop;
    120         PNODE q = NULL;
    121         if(p != pS->pBottom)
    122         {
    123 q = p->pNext;
    124 free(p);
    125 p = q;
    126     }
    127 pS->pTop = pS->pBottom;
    128     }
    129 /*此種方法待驗證
    130 while (!is_empty(pS)) 
    131 {
    132 PNODE p = pS->pTop;
    133 pS->pTop = p->pNext;
    134 free(p);
    135 p=NULL;
    136 }
    137 */
    138 return;
    139 }
  • 線性結構的兩種常見應用之二:佇列

    • 定義:一種可以實現“先進先出”的儲存結構

    • 分類

      • 鏈式佇列——用連結串列實現

      • 靜態佇列——用陣列實現

        1. 靜態佇列通常都必須是迴圈佇列

        2. 迴圈佇列的講解

          • 靜態佇列為什麼必須是迴圈佇列

          • 迴圈佇列需要幾個引數來確定

            需要兩個引數來確定佇列,front|rear;

          • 迴圈佇列各個引數的含義

            1. 佇列初始化:front和rear的值都是0

            2. 佇列非空:front代表的是佇列的第一個元素,rear代表的是佇列的最後一個有效元素的下一個位置

            3. 佇列空:front和rear的值相等,但不一定是0

          • 迴圈佇列入隊偽演算法

            1. 將值存入rear所代表的位置

            2. rear = (rear + 1) % 陣列的長度

          • 迴圈隊列出隊偽演算法

            1. front = (front + 1) % 陣列的長度

          • 如何判斷迴圈佇列是否為空

            if (front == rear)

          • 如何判斷迴圈佇列是否已滿

            兩種方式:

            1. if ((rear + 1 % 陣列的長度) == front)

            2. 元素個數=陣列長度-1

        3. 佇列的具體應用:所有和時間有關的操作都與佇列有關

          樣例程式碼

            1 # include <stdio.h>
            2 # include <malloc.h>
            3 # include <stdboo.h>
            4   5 typedef struct Queue
            6 {
            7 int * pBase;//迴圈陣列的首地址
            8 int front;//隊頭
            9 int rear;//隊尾
           10 }QUEUE;
           11  12 //初始化佇列
           13 void init(QUEUE *);
           14 //判斷佇列是否為空
           15 bool is_empty(QUEUE *);
           16 //佇列是否已滿
           17 bool is_full(QUEUE *);
           18 //插入佇列 入隊
           19 bool en_queue(QUEUE *,int val);
           20 //遍歷佇列
           21 void traverse_queue(QUEUE *);
           22 //出隊
           23 bool out_queue(QUEUE *,int *);
           24  25 int main(void)
           26 {
           27  int val;//出隊的元素
           28  QUEUE Q;
           29  init(&Q);
           30  en_queue(&Q,1);
           31  en_queue(&Q,2);
           32  en_queue(&Q,3);
           33  en_queue(&Q,4);
           34  en_queue(&Q,5);
           35  en_queue(&Q,6);
           36  traverse_queue(&Q);
           37  if(out_queue(&Q,&val))
           38  {
           39      printf("The elem out queue is %d .",val);
           40  }
           41  return 0;
           42 }
           43  44 void init(QUEUE * pQ)
           45 {
           46  pQ->pBase = (int *)malloc(sizeof(int) * 6);
           47  pQ->front = 0;
           48  pQ->rear = 0;
           49 }
           50  51 bool is_empty(QUEUE * pQ)
           52 {
           53  return pQ->front == pQ->rear? true:false;
           54 }
           55  56 bool is_full(QUEUE * pQ)
           57 {
           58  return pQ->front == (pQ->rear + 1) % 6 ? true:false;
           59 }
           60  61 //入隊
           62 bool en_queue(QUEUE * pQ,int val)
           63 {
           64  if(is_full(pQ))
           65     return false;
           66  else
           67  {
           68      pQ->pBase[pQ->rear] = val;
           69      pQ->rear = (pQ->rear + 1) % 6;
           70      return true;
           71  }
           72  
           73 }
           74  75 //遍歷佇列
           76 void traverse_queue(QUEUE * pQ)
           77 {
           78     if(is_empty(pQ))
           79  {
           80      printf("The queue is empty!");
           81      return;
           82  }
           83  else
           84  {
           85      int val;
           86      while(pQ->front != pQ->rear)
           87      {
           88          printf("%d ",pQ->pBase[pQ->front]);
           89          pQ->front = (pQ->front + 1) % 6;
           90      }
           91      printf("\n");
           92      return;
           93  }
           94 }    
           95  96 //出隊
           97 bool out_queue(QUEUE * pQ,int *pVal)
           98 {
           99  if(is_empty(pQ))
          100  {
          101      printf("The queue is empty.");
          102      return false;
          103  }
          104  else
          105  {
          106      *pVal = pQ->pBase[pQ->front];
          107      pQ->front = (pQ->front + 1) % 6;
          108      return true;
          109  }
          110

主題:遞迴

定義:一個函式自己直接或間接呼叫自己

遞迴滿足三個條件

  1. 遞迴必須得有一個明確的中止條件

  2. 該函式所處理的資料規模必須在遞減

  3. 這個轉化必須是可解的

迴圈和遞迴

  • 遞迴

    • 易於理解

    • 速度慢

    • 儲存空間大

  • 迴圈

    • 不易理解

    • 速度快

    • 儲存空間小

函式的呼叫:

  • 當在一個函式的執行期間呼叫另一個函式時,在執行被調函式之前,系統需要完成三件事:

    1. 將所有的實際引數、返回地址等資訊傳遞給被調函式儲存。

    2. 為被調函式的區域性變數(也包括行參)分配儲存空間。

    3. 將控制轉移到被調函式的入口。

  • 從被調函式返回函式之前,系統也要完成三件事:

    1. 儲存被調函式的返回結果。

    2. 釋放被調函式所佔的儲存空間。

    3. 依照被調函式儲存的返回地址將控制轉移到呼叫函式。

  • 當有多個函式相互呼叫時,按照”後呼叫先返回“的原則,上述函式之間資訊傳遞和控制轉移必須藉助”棧“來實現,即系統將整個程式執行時所需的資料空間安排在一個棧中,每當呼叫一個函式時,就在棧頂分配一個儲存區,進行壓棧操作,每當一個函式退出時,就釋放它的儲存區,就做出棧操作,當前執行的函式永遠都在棧頂位置。

  • A函式呼叫A函式和A函式呼叫B函式在計算機看來是沒有任何區別的,只不過用我們日常的思維方式理解比較怪異而已!

  1. 1+2+3+...+100的和


    程式碼實現
     1 # include <stdio.h>
     2  3 int sum(int);
     4  5 int main(void)
     6 {
     7     int n = 100;
     8     printf("%d\n",sum(n));
     9  return 0;
    10 }
    11 12 int sum(int n)
    13 {
    14  if(1 == n)
    15      return 1;
    16  else
    17      return n + sum(n-1);
    18
  2. 求階乘

    程式碼實現
     1 # include <stdio.h>
     2  3 long multi(int);
     4  5 int main(void)
     6 {
     7     printf("%ld\n",multi(6));
     8  return 0;
     9 } 
    10 11 long multi(int n)
    12 {
    13  if (1 == n)
    14     return 1;
    15  else
    16     return n * multi(n - 1);
    17 }
  3. 漢諾塔

    虛擬碼:

    如果只有一個盤子:

    直接將盤子從A柱子移到C柱子

    否則:

    先將A柱子上的n-1個盤子藉助C柱子移到B柱子

    再直接將盤子從A柱子移到C柱子

    最後再將B柱子上的盤子藉助A柱子移到C柱子上

    程式碼實現樣例:

     1 # include <stdio.h>
     2  3 void hanoi(int,char,char,char);
     4  5 int main(void)
     6 {
     7     //柱子編號
     8     char ch1 = 'A';
     9     char ch2 = 'B';
    10     char ch3 = 'C';
    11     //盤子數量
    12     int n;
    13     printf("請輸入盤子的數量");
    14     scanf("%d",&n);
    15     hanoi(n,'A','B','C');
    16     
    17     return 0;
    18 }
    19 20 void hanoi(int n,char 'A',char 'B',char 'C')
    21 {
    22     if (1 == n)
    23         printf("編號為%d的盤子:%c-->%c\n",n,'A',"C");
    24     else
    25     {
    26         hanoi(n-1,A,C,B);
    27         printf("編號為%d的盤子:%c-->%c\n",n,'A',"C");
    28         hanoi(n-1,B,A,C);
    29     }
    30
  4. 走迷宮

    A*(A-Star)演算法實現

遞迴的應用
  • 樹和森林就是以遞迴的方式定義的

  • 樹和圖的很多演算法

  • 很多數學公式:例如斐波拉契數列

非線性結構

樹的定義
  • 專業定義:

    1. 有且只有一個稱為根的節點

    2. 有若干個互不相交的子樹,這些子樹本身也是一顆樹

  • 通俗定義:

    1. 樹是由節點和邊組成

    2. 每個節點只有一個父節點但可以有多個子節點

    3. 但有一個節點例外,該節點沒有父節點,此節點稱為根節點

  • 專業術語:

    • 節點,父節點,子節點

    • 子孫,堂兄弟

    • 深度:從根節點到最底層節點的層數稱之為深度,根節點是第一層

    • 葉子節點:沒有子節點的節點

    • 非終端節點:實際就是非葉子節點

    • 度:子節點的個數

樹的分類
  • 一般樹:任意一個節點的子節點的個數都不受限制

  • 二叉樹:任意一個節點的子節點個數最多兩個,且子節點的位置不可更改

    • 分類

      • 一般二叉樹

      • 滿二叉樹:在不增加樹層數的前提下,無法再多新增一個節點的二叉樹就是滿二叉樹

      • 完全二叉樹:如果只是刪除了滿二叉樹最底層最右邊的連續若干個節點,這樣形成的二叉樹就是完全二叉樹。(滿二叉樹是完全二叉樹的一個特例)

  • 森林:n個互不相交的樹的集合

樹的儲存
  • 二叉樹的儲存

    • 連續儲存【完全二叉樹】

      優點:查詢某個節點的父節點和子節點(也包括判斷有沒有子節點)

      缺點:耗用記憶體空間過大

    • 鏈式儲存

  • 一般樹的儲存

    • 雙親表示法:求父節點方便

    • 孩子表示法:求子節點方便

    • 雙親孩子表示法:求父節點和子節點都很方便

    • 二叉樹表示法:把一個普通樹轉化成二叉樹來儲存

      具體轉換方法:

      設法保證任意一個節點的左指標域指向它的第一個孩子,右指標域指向它的兄弟,只要滿足此條件,就可以把一個普通樹轉化為二叉樹。

      一個普通樹轉化成的二叉樹一定沒有右子樹

  • 森林的儲存

    先把森林轉化為二叉樹,再儲存二叉樹:

    將相鄰的父節點依次作為節點的右子樹再對各父節點進行轉化

樹的操作
  • 遍歷

    • 先序遍歷【先訪問根節點】

      1. 先訪問根節點

      2. 再先序訪問左子樹

      3. 最後先序訪問右子樹

        例子:

                A
          / \
        B   C
            / \   \
        D   E   F
          / \   \ / \
        G   H   I J k

        先序遍歷結果:ABDGHEICFJK

    • 中序遍歷【中間訪問根節點】

      1. 中序遍歷左子樹

      2. 再訪問根節點

      3. 再中序遍歷右子樹

        例子:

                A
          / \
        B   C
            / \   \
        D   E   F
          / \   \ / \
        G   H   I J k

        中序遍歷結果:GDHBEIACJFK

    • 後續遍歷【最後訪問根節點】

      1. 先中序遍歷左子樹

      2. 再中序遍歷右子樹

      3. 最後遍歷根節點

        例子:

                A
          / \
        B   C
            / \   \
        D   E   F
          / \   \ / \
        G   H   I J k

        後序遍歷結果:GHDIEBJKFCA

  • 已知兩種遍歷序列求原始二叉樹

    通過先序和中序或者中序和後序我們可以還原出原始二叉樹,但是通過先序和後序是無法還原出原始二叉樹;

    換句話,只有通過先序和中序或者中序和後序,我們才可以唯一的確定一個二叉樹。

    例子1:

    先序:ABCDEFGH
    中序:BDCEAFHG
    求後序?
    分析:按照先序的定義,A為最外層根節點,按照中序的定義和前面的結論可知BDCE為A節點的左子樹節點,FHG為A節點的右子樹,再依次按照兩個遍歷定義可以推出原始二叉樹為:
    A
      / \
    B   F
    \ \
    C G
    / \ /
      D E H
    那麼此二叉樹的後序為:DECBHGFA

    例子2:

    先序:ABDGHCEFI
    中序:GDHBAECIF
    求後序?
    分析:按照先序的定義得到A為最外層根節點,再根據中序結果可知GDHB為A的左子樹,ECIF為A的右子樹;B先出現在先序結果中可知B為左子樹的根節點,再根據中序結果知B節點沒有右子樹,GDH均為B節點的左子樹,再根據先序結果D先出現,知D為B左子樹的根節點,再根據先序發現G在D的後面且中序中G在D的前面得出G為D左子樹的根節點,那麼D的右子樹的根節點就是H了,依次類推A的右子樹,得出原始二叉樹為:
        A
        / \
      B   C
      /   / \
    D   E   F
    / \     /
    G   H   I
    那麼此二叉樹的後序為:GHDBEIFCA

    例子3:

    中序:BDCEAFHG
    後序:DECBHGFA
    求先序?
    分析:由後序結果知A為最外層根節點,再根據中序結果知BDCE為A節點的左子樹,FHG為A的右子樹;A的左子樹中B最靠近A那麼根據後序規則得出B為左子樹的根節點,再根據中序結果B在結果的第一位,由中序規則知B沒有左子樹,DCE均為B的右子樹,在DCE中後序結果C最靠近B,得出C為B的右子樹的根節點,再依據中序結果知C前面是D後面是E得出D為C的左子樹,E為C的右子樹,同理可以推出A的右子樹,得出原始二叉樹為:
    A
      / \
    B   F
      \   \
      C   G
      / \ /
    D   E H
    那麼此二叉樹的先序為:ABCDEFGH
  • 二叉樹的程式碼實現舉例

      1 # include <stdio.h>
      2 # include <malloc.h>
      3   4 typedef struct BinaryTree
      5 {
      6     char data;//節點值 資料域
      7     struct BinaryTree * pLchild;//左子樹 左指標域
      8     struct BinaryTree * pRchild;//右子樹 右指標域
      9 }BINARYTREE,* PBINARYTREE;
     10  11 //建立一個靜態二叉樹
     12 PBINARYTREE create_binary_tree(void);
     13 //先序遍歷
     14 void pre_traversal(PBINARYTREE);
     15 //中序遍歷
     16 void in_traversal(PBINARYTREE);
     17 //後序遍歷
     18 void post_traversal(PBINARYTREE);
     19  20 int main(void)
     21 {
     22     PBINARYTREE pT = NULL;
     23     pT = create_binary_tree();
     24     printf("先序遍歷結果為:");
     25     pre_traversal(pT);
     26     printf("\n中序遍歷結果為:");
     27     in_traversal(pT);
     28     printf("\n後序遍歷結果為:");
     29     post_traversal(pT);
     30     return 0;
     31 }
     32  33 /**建立以下結果的二叉樹,此二叉樹結構是上面二叉樹遍歷的例子,可對比結果是否正確
     34         A
     35        / \
     36       B   C
     37      / \   \
     38     D   E    F
     39    / \   \  / \
     40   G   H   I J  k
     41 */
     42 PBINARYTREE create_binary_tree(void)
     43 {
     44     //為每個二叉樹節點申請記憶體空間
     45     PBINARYTREE pA = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     46     PBINARYTREE pB = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     47     PBINARYTREE pC = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     48     PBINARYTREE pD = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     49     PBINARYTREE pE = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     50     PBINARYTREE pF = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     51     PBINARYTREE pG = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     52     PBINARYTREE pH = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     53     PBINARYTREE pI = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     54     PBINARYTREE pJ = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     55     PBINARYTREE pK = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     56     //給每個二叉樹節點的資料域賦值
     57     pA->data = 'A';
     58     pB->data = 'B';
     59     pC->data = 'C';
     60     pD->data = 'D';
     61     pE->data = 'E';
     62     pF->data = 'F';
     63     pG->data = 'G';
     64     pH->data = 'H';
     65     pI->data = 'I';
     66     pJ->data = 'J';
     67     pK->data = 'K';
     68     //給每個二叉樹節點的左右指標域賦值
     69     pA->pLchild = pB; 
     70     pA->pRchild = pC; 
     71     pB->pLchild = pD; 
     72     pB->pRchild = pE;
     73     pC->pLchild = NULL; 
     74     pC->pRchild = pF;
     75     pD->pLchild = pG; 
     76     pD->pRchild = pH;
     77     pE->pLchild = NULL; 
     78     pE->pRchild = pI;
     79     pF->pLchild = pJ; 
     80     pF->pRchild = pK;
     81     pG->pLchild = pG->pRchild = NULL;
     82     pH->pLchild = pH->pRchild = NULL;
     83     pI->pLchild = pI->pRchild = NULL;
     84     pJ->pLchild = pJ->pRchild = NULL;
     85     pK->pLchild = pK->pRchild = NULL;
     86     
     87     return pA;
     88 }
     89  90 void pre_traversal(PBINARYTREE pT)
     91 {
     92     if(NULL != pT)
     93     {
     94         printf("%c\t",pT->data);
     95         if(NULL != pT->pLchild)
     96             pre_traversal(pT->pLchild);
     97 if(NULL != pT->pRchild)
     98 pre_traversal(pT->pRchild);
     99     }
    100 return;
    101 }
    102 103 void in_traversal(PBINARYTREE pT)
    104 {
    105 if(NULL != pT)
    106     {
    107 if(NULL != pT->pLchild)
    108 in_traversal(pT->pLchild);
    109 printf("%c\t",pT->data);
    110 if(NULL != pT->pRchild)
    111 in_traversal(pT->pRchild);
    112     }
    113 return;
    114 }
    115 116 void post_traversal(PBINARYTREE pT)
    117 {
    118 if(NULL != pT)
    119     {
    120 if(NULL != pT->pLchild)
    121 post_traversal(pT->pLchild);
    122 if(NULL != pT->pRchild)
    123 post_traversal(pT->pRchild);
    124 printf("%c\t",pT->data);
    125     }
    126 return;
    127 }

     

樹的應用
  • 樹是資料庫中資料組織的一種重要形式

  • 作業系統子父程序的關係本身就是一個樹

  • 面嚮物件語言中類的繼承關係

  • 赫夫曼樹

視訊中沒有具體講解

查詢和排序

查詢:折半查詢
排序

關於排序演算法,推薦去中文維基百科上面檢視,有動態排序過程,有助於理解演算法本質!

排序演算法

  1. 氣泡排序

     1 //簡單氣泡排序舉例【升序】
     2  3 # include <stdio.h>
     4  5 void bubble_sort(int *,int);
     6  7 int main(void)
     8 {
     9     int i;
    10     int len = 6;
    11     int arr[len] = {2,10,8,5,3,1};
    12     
    13     bubble_sort(arr,len);
    14     for(i = 0;i < 6;i++)
    15         printf("%d ",arr[i]);
    16     printf("\n");
    17     return 0;
    18 }
    19 20 void bubble_sort(int * arr,int len)
    21 {
    22     int i,j,t;
    23     for(i = 0; i < len; i++)
    24     {
    25         for(j = i+1; j < len-1; j++)
    26         {
    27             if(arr[i] > arr[j])
    28             {
    29                 t = arr[i];
    30                 arr[i] = arr[j];
    31                 arr[j] = t;
    32             }
    33         }
    34     }
    35 }
  2. 插入排序

     1 //直接插入排序【升序】
     2  3 # include <stdio.h>
     4  5 void insertion_sort(int *,int);
     6  7 int main(void)
     8 {
     9     int i;
    10     int len = 6;//陣列長度
    11     int arr[len] = {2,10,8,5,3,1};
    12     insertion_sort(arr,len);
    13     for(i = 0;i < 6;i++)
    14         printf("%d ",arr[i]);
    15     printf("\n");
    16 }
    17 18 void insertion_sort(int * arr,int len)
    19 {
    20     int i,j,t;
    21     for (i=1; i<len; i++)
    22     {
    23         if(arr[i]<arr[i-1])
    24         {
    25             t = arr[i];
    26             j = i - 1;
    27             for(j; j>=0 && arr[j]>t;j--)
    28             {
    29                 arr[j+1] = arr[j];
    30             }
    31             a[j+1] = t;
    32         }
    33     }
    34 }
  3. 選擇排序

     1 //直接選擇排序【升序】
     2  3 # include <stdio.h>
     4  5 void selection_sort(int *,int);
     6  7 int main(void)
     8 {
     9     int i;
    10     int len = 6;//陣列長度
    11     int arr[6] = {2,10,8,5,3,1};
    12     selection_sort(arr,len);
    13     for(i = 0;i < 6;i++)
    14         printf("%d ",arr[i]);
    15     printf("\n");
    16     return 0;
    17 }
    18 19 void selection_sort(int * arr,int len)
    20 {
    21     int i,j,t,min;
    22     for(i=0; i<len-1; i++)
    23     {
    24         for(min=i,j=i+1; j<len; j++)
    25         {
    26             if(arr[min] < arr[j])
    27                 min = j;         
    28         }
    29         if(min != j)
    30         {
    31             t = arr[i];
    32             arr[i] = arr[min];
    33             arr[min] = t;
    34          }
    35     }
    36 }

     

  4. 快速排序

     1 # include <stdio.h>
     2  3 void quick_sort(int *,int,int);
     4 int find_pos(int *,int,int);
     5  6 int main(void)
     7 {
     8     int i,len,low,high;
     9     low = 0;
    10     high = 6;
    11     int arr[7] = {13,2,1,3,8,5,1};
    12     quick_sort(arr,low,high);//low表示起始位置,high表示結束位置
    13     for(i = 0;i < 7;i++)
    14         printf("%d ",arr[i]);
    15     printf("\n");
    16     return 0;
    17 }
    18 19 void quick_sort(int * arr,int low,int high)
    20 {
    21     int pos;
    22     if (low < high)
    23     {
    24         pos = find_pos(arr,low,high);
    25         quick_sort(arr,low,pos-1);
    26         quick_sort(arr,pos+1,high);
    27     }
    28 }
    29 30 int find_pos(int * arr,int low,int high)
    31 {
    32     int val = arr[low];
    33     while(low < high)
    34     {
    35         while(low < high && arr[high] >= val)
    36             --high;
    37         arr[low] = arr[high];
    38         while(low < high && arr[low] <= val)
    39             ++low;
    40         arr[high] = arr[low];
    41     }
    42     arr[low] = val;
    43     return high;
    44 }

     

  5. 歸併排序

    摘自中文維基百科-歸併排序

     1 # include <stdio.h>
     2  3 void merge_sort(int arr[], const int len);
     4 void merge_sort_recursive(int arr[], int reg[], int start, int end);
     5  6 int main(void)
     7 {
     8     int i;
     9     int arr[7] = {13,2,1,3,8,5,1};
    10     merge_sort(arr,7);
    11     for(i = 0;i < 7;i++)
    12         printf("%d ",arr[i]);
    13     printf("\n");
    14     return 0;
    15 }
    16 17 void merge_sort_recursive(int arr[], int reg[], int start, int end) {
    18     if (start >= end)
    19         return;
    20     int len = end - start, mid = (len >> 1) + start;
    21     int start1 = start, end1 = mid;
    22     int start2 = mid + 1, end2 = end;
    23     merge_sort_recursive(arr, reg, start1, end1);
    24     merge_sort_recursive(arr, reg, start2, end2);
    25     int k = start;
    26     while (start1 <= end1 && start2 <= end2)
    27         reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
    28     while (start1 <= end1)
    29         reg[k++] = arr[start1++];
    30     while (start2 <= end2)
    31         reg[k++] = arr[start2++];
    32     for (k = start; k <= end; k++)
    33         arr[k] = reg[k];
    34 }
    35 36 void merge_sort(int arr[], const int len) {
    37     int reg[len];
    38     merge_sort_recursive(arr, reg, 0, len - 1);
    39

總結

堅持就是勝利!

 

 

 

本篇內容是根據B站郝斌資料結構的學習筆記,本篇筆記是用markdown根據Typora編寫,copy到部落格有些格式微調,如發現錯誤望留言指正,不勝感激!

如有侵權請聯絡我刪除:[email protected]

如想轉載請註明出處謝謝!

ps:本篇篇幅較長,請慢慢享用!

 

資料結構

概述

  • 資料結構定義

    我們如何把現實中大量而複雜的問題以特定的資料型別和特定的儲存結構儲存到主儲存器(記憶體)中,以及在此基礎上為實現某個功能(比如查詢某個元素,刪除某個元素,對元素進行排序等)而執行的相應操作,這個相應的操作也叫演算法。

    資料結構 = 個體的儲存 + 個體的關係儲存

    演算法 = 對儲存資料的操作

  • 演算法定義

    • 通俗的說演算法是解題的方法和步驟

    • 衡量演算法的標準

      1. 時間複雜度:程式大概要執行的次數,而非執行的時間。

      2. 空間複雜度:程式執行過程中大概所佔用的最大記憶體空間。

      3. 難易程度:用易懂,避免過於複雜。

      4. 健壯性

  • 資料結構的地位

    資料結構是軟體中最核心的課程

    程式=資料的儲存+資料的操作+可以被計算機執行的語言

預備知識

  1. 指標

    • 指標的重要性:指標是C語言的靈魂

    • 定義:

      • 地址:記憶體單元的編號,從0開始的非負整數

      • 指標:指標就是地址,地址就是指標;指標變數是存放記憶體單元地址的變數;指標的本質是一個操作受限的非負整數。

    • 分類:基本型別的指標;指標和陣列的關係

  2. 結構體

    • 為什麼會出現結構體:為了表示一些複雜的資料,而普通的基本型別變數無法滿足要求;

    • 定義:結構體是使用者根據實際需要自己定義的複合數型別;

    • 如何使用結構體

      //定義結構體
      struct Student
      {
          int sid;
          char name[200];
          int age;
      };
      • //整體賦值,類似於Java中new類的建構函式 
        struct Student st = {1001,"zhangsan",18};
        //單個賦值
        st.id=1001;
        strcpy(st.name,"zhangsan");
        st.age=18; 
      • //通常使用指標的方式賦值
        struct Student *pst;
        //pst所指向的結構體變數中的sid這個成員
        pst->sid=1001;
        strcpy(pst->name,"lisi");
        pst->age=19
         
    • 注意事項:結構體變數不能算術計算,但是可以賦值;普通結構體變數和結構體指標變數作為函式傳參的問題,推薦使用傳遞結構體指標的方式,這樣效率高節約記憶體。

  3. 動態記憶體的分配和釋放   

 樣例程式碼:    

 1  1 一維陣列:
 2  2 
 3  3 # include <stdio.h>
 4  4 # include <malloc.h>
 5  5 6  6 int main(void)
 7  7 {
 8  8  //靜態分配陣列
 9  9  int a[5] = {2,3,5,6,9};
10 10  //len為一維陣列的長度,可以根據需求動態分配長度
11 11  int len;
12 12  printf("請輸入你需要分配的陣列長度:len=");
13 13  scanf("%d",&len);//len=5
14 14  //malloc為分配記憶體的函式,返回第一個位元組的地址,但是預設返回是一個乾地址,沒有實際意義,必須加強制型別轉換為指定的指標型別才有意義,(int *)表示強轉為int型別的指標,那麼返回的地址指向的就是第一個元素的地址,那麼第二個元素的地址就是第一個地址向後挪一位
15 15  int * pArr = (int *)malloc(sizeof(int) * len);
16 16  *pArr = 2;//類似於 a[0]=4,因為陣列名就是指向了第一個元素的地址,跟*pArr一樣
17 17  pArr[1] = 3; //類似於 a[1]=3
18 18  
19 19  free(pArr);//把pArr所代表的動態分配的20個位元組的記憶體釋放
20 20  
21 21  return 0;
22 22 }
  1. 跨函式使用記憶體

     1 # include <stdio.h>
     2 # include <malloc.h>
     3  4 struct Student
     5 {
     6  int sid;
     7  int age;
     8 };
     9 10 struct Student * CreateStudent(void);
    11 void ShowStudent(struct Student *);
    12 13 int main(void)
    14 {
    15     struct Student * ps;
    16     ps = CreateStudent();
    17  ShowStudent(ps);
    18  return 0;
    19 }
    20 21 void ShowStudent(struct Student * pst)
    22 {
    23  printf("%d %d",pst->sid,pst->age);
    24 }
    25 26 struct Student * CreateStudent(void)
    27 {
    28     struct Student * p = (struct Student *)malloc(sizeof(struct Student));
    29  p->sid = 1001;
    30  p->age = 18;
    31  return p;
    32 }
  2. typedef函式的使用

     1 typedef int INT; // 相當於給int起了一個別名 INT
     2 typedef struct Student
     3 {
     4  int sid;
     5  char name[100];
     6  char sex;
     7 } ST; //ST st 就相當於 struct Student st,給struct Student 起了別名ST,這樣簡潔了程式碼
     8 typedef struct Student
     9 {
    10  int sid;
    11  char name[100];
    12  char sex;
    13 } * ST; //ST就相當於struct Student *  

線性結構

把所有節點用一條直線串起來

  • 連續儲存【陣列】

    1. 什麼叫陣列:元素型別相同,大小相等

    2. 陣列的優缺點

      • 優點:存取速度很快

      • 缺點:插入刪除元素很慢

      陣列例子

        1 # include <stdio.h>
        2 # include <malloc.h>
        3 # include <stdlib.h>
        4 # include <stdbool.sh>
        5   6 struct Arr
        7 {
        8     int * pBase;//陣列首地址
        9     int cnt;//當前陣列內已有元素長度
       10     int len;//陣列長度
       11 };
       12  13 //初始化陣列
       14 void init_arr(struct Arr * pArr,int length);
       15 //獲取某個位置上的元素
       16 int get(int pos);
       17 //判斷陣列是否已經滿了
       18 bool is_full(struct Arr * pArr);
       19 //判斷陣列是否為空
       20 bool is_empty(struct Arr * pArr);
       21 //列印陣列內所有元素
       22 void show_arr(struct Arr * pArr);
       23 //向陣列中追加元素
       24 void append_arr(struct Arr * pArr,int val);
       25 //向陣列某個位置插入元素
       26 void insert_arr(struct Arr * pArr,int pos,int val);
       27 //排序
       28 void sort_arr (struct Arr * pArr);
       29  30 int main(void)
       31 {
       32     struct Arr arr;
       33     init_arr(&arr,6);
       34     
       35     
       36     return 0;
       37 }
       38  39 void init_arr(struct Arr * pArr,int length)
       40 {
       41     pArr->pBase = (int *)malloc(sizeof(struct Arr) * length);
       42     if ( NULL == pArr->pBase)
       43     {
       44         printf("動態記憶體分配失敗\n");
       45         exit(-1);
       46     }
       47     else 
       48     {
       49         pArr->len = length;
       50         pArr->cnt = 0;
       51     }
       52     return;
       53 }
       54  55 bool is_empty(struct Arr *pArr)
       56 {
       57     return 0 == pArr->cnt ? true:false;
       58 }
       59  60 bool is_full(struct Arr * pArr)
       61 {
       62     return pArr->cnt == pArr->len ? true:false;
       63 }
       64  65 void show_arr(struct Arr * pArr)
       66 {
       67     int i;
       68     if ( is_empty(pArr) )
       69     {
       70         printf("陣列為空");
       71     }
       72     else 
       73     {
       74         for (i = 0;i<pArr->cnt;i++)
       75         {
       76             printf("%d ",pArr->pBase[i]);
       77         }
       78         printf("\n");
       79     }
       80     return;
       81 }
       82  83 void append_arr(struct Arr * pArr,int val)
       84 {
       85     if( is_full(pArr) )
       86     {
       87         printf("陣列已滿,不能再追加!\n");
       88     }
       89     else 
       90     {
       91         pArr->pBase[pArr->cnt]=val;
       92         ++ pArr->cnt;
       93     }
       94     return;
       95 }
       96  97 void insert_arr(struct Arr * pArr,int pos,int val)
       98 {
       99     if ( is_full(pArr) )
      100     {
      101         printf("陣列已滿,無法插入!\n");
      102         return;
      103     }
      104     if ( pos < 1 || pos > pArr->cnt)
      105     {
      106         printf("指定位置有誤,無法插入!\n");
      107         return;
      108     }
      109     
      110     for (int i=pArr->cnt; i<pos-1; --i)
      111     {
      112         pArr->pBase[i+1]=pArr->pBase[i];
      113     }
      114     pArr->pBase[pos-1]=val;
      115     pArr->cnt ++;
      116     return;
      117 }
      118 119 void inversion_arr (struct Arr * pArr)
      120 {
      121 int i;
      122 int j = pArr-> cnt -1;
      123 int t;
      124 
      125 while (i < j) 
      126     {
      127 t = pArr->pBase[i];
      128 pArr->pBase[i] = pArr->pBase[j];
      129 pArr->pBase[j] = t;
      130 ++i;
      131 --j;
      132     }
      133 return;
      134 }
      135 136 void sort_arr (struct Arr * pArr)
      137 {
      138 int i,j,t;
      139 140 for (i=0; i<pArr->cnt-1; ++i)
      141     {
      142 for (j=i+1; j<pArr->cnt; ++j)
      143     {
      144 if (pArr->pBase[i]>pArr->pBase[j])
      145     {
      146 t = pArr->pBase[j];
      147 pArr->pBase[j]=pArr->pBase[i];
      148 pArr->pBase[i]=t;
      149     }
      150     }
      151     }
      152 return;
      153 }
  • 離散結構【連結串列】

    • 定義:n個節點離散分配,彼此通過指標相連,每個節點只有一個前驅節點同時每個節點只有一個後續節點,首節點沒有前驅節點,尾節點沒有後續節點。

      專業術語

      • 首節點:存放第一個有效資料的節點

      • 尾節點:存放最後一個有效資料的節點

      • 頭結點:位於首節點之前的一個節點,頭結點並不存放有效的資料,加頭結點的目的主要是為了方便對連結串列的操作

      • 頭指標:指向頭結點的指標變數

      • 尾指標:指向尾節點的指標變數

    • 確定一個連結串列需要幾個引數:只需要一個頭指標引數,因為我們通過頭指標可以推算出連結串列的其他所有資訊

      例子

       1 # include <stdio.h>
       2  3 typedef struct Node
       4 {
       5  int data;//資料域
       6  struct Node * PNext;//指標域
       7 } NODE, *PNODE;//NODE等價於struct Node,PNODE等價於struct Node *
       8  9 int main(void)
      10 {   
      11  return 0;
      12 }
    • 分類

      • 單鏈表:每一個節點只有一個指標域

      • 雙鏈表:每一個節點有兩個指標域

      • 迴圈連結串列:能通過任何一個節點找到其他所有的節點

      • 非迴圈連結串列:不能通過任何一個節點找到其他所有的節點

        迴圈連結串列屬於雙鏈表的一種特殊形式,即迴圈連結串列是雙鏈表的一個子集。

    • 優缺點

      • 優點:空間沒有限制,插入和刪除元素很快

      • 缺點:存取速度很慢

    • 演算法

      演算法:

      • 俠義的演算法是與資料的儲存方式密切相關

      • 廣義的演算法是與資料的儲存方式無關

      泛型:利用某種技術達到的效果就是:不同的儲存方式,執行的操作是一樣的

      • 遍歷

      • 查詢

      • 清空

      • 銷燬

      • 求長度

      • 排序

      • 刪除節點

      • 插入節點

      程式碼實現

        1 # include <stdio.h>
        2 # include <malloc.h>
        3 # include <stdbool.h>
        4   5 typedef struct Node
        6 {
        7 int data;//資料域
        8 struct Node * pNext;//指標域
        9 }NODE,*PNODE;//NODE相當於struct Node,*PNODE相當於struct Node *
       10  11 //建立連結串列
       12 PNODE create_list(void);
       13 //遍歷連結串列
       14 void traverse_list(PNODE pHead);
       15 //判斷是否為空
       16 bool is_empty(PNODE pHead);
       17 //返回連結串列長度
       18 int length_list(PNODE pHead);
       19 //在指定節點處插入某個元素
       20 bool insert_list(PNODE,int,int);
       21 //刪除指定位置的元素
       22 bool delete_list(PNODE,int,int *);
       23 //對連結串列排序
       24 void sort_list(PNODE pHead);
       25  26 int main(void)
       27 {
       28 PNODE pHead = NULL;//定義頭結點指標
       29 pHead = create_list();
       30 return 0;
       31 }
       32  33 PNODE create_list(void)
       34 {
       35 int len;//連結串列成員個數,由使用者輸入
       36 int i;
       37 int val;//連結串列成員值,由使用者輸入
       38  39 PNODE pHead = (PNODE)malloc(sizeof(NODE));//定義頭結點指標
       40 if (NULL == pHead)
       41 {
       42   printf("分配記憶體失敗,程式結束");
       43   exit(-1);
       44 }
       45  46 printf("請輸入連結串列長度,len=");
       47 scanf("%d",&len);
       48  49 PNODE pTail = pHead;
       50 pTail->pNext = NULL;
       51  52 for(i=0; i < len; i++)
       53 {
       54   PNODE pNew = (PNODE)malloc(sizeof(NODE));
       55   if (NULL == pNew)
       56      {
       57     printf("分配記憶體失敗,程式結束");
       58     exit(-1);
       59      }
       60   printf("請輸入要插入連結串列的值,val=");
       61      scanf("%d",&val);
       62   
       63   pNew->data = val;
       64   pTail->pNext = pNew;
       65   pNew->pNext = NULL;
       66   pTail = pNew;
       67 }
       68 return pHead;
       69 }
       70  71 void traverse_list(PNODE pHead)
       72 {
       73  PNODE p = pHead->pNext;
       74  if (NULL != p)
       75  {
       76      printf("%d\t",p->data);
       77      p = p->pNext;
       78  }
       79  printf("\n");
       80  return;
       81 }
       82  83 bool is_empty(PNODE pHead)
       84 {
       85  if (NULL == pHead->pNext)
       86     return true;
       87  else
       88      return false;
       89 }
       90  91 int length_list(PNODE pHead)
       92 {
       93  int len = 0;
       94  PNODE p = pHead->pNext;
       95  while(NULL != p)
       96  {
       97      ++len;
       98      p = p->pNext;
       99  }
      100  return len;
      101 }
      102 103 bool insert_list(PNODE pHead,int pos,int val)
      104 {
      105  int i;
      106  PNODE p = pHead;
      107  //迴圈到p指向pos-1的位置
      108  while( NULL != p && i<pos-1)
      109  {
      110      p = p->pNext;
      111      ++i;
      112  }
      113  if (NULL == p || i > pos -1)
      114  {
      115      return false;
      116  }
      117  //插入的數申請記憶體
      118  PNODE pNew = (PNODE)malloc(sizeof(NODE));
      119  if (NULL == pNew)
      120     {
      121         printf("分配記憶體失敗,程式終止!\n");
      122         exit(-1);
      123     }
      124  pNew->data = val;
      125  PNODE q = p->pNext;
      126  p->pNext = pNew;
      127  pNew->pNext = q;
      128  return ture;
      129 } 
      130 131 bool delete_list(PNODE pHead,int pos,int *pVal)
      132 {
      133  int i;
      134  PNODE p = pHead;
      135  //迴圈到p指向pos-1的位置
      136  while( NULL != p->pNext && i<pos-1)
      137  {
      138      p = p->pNext;
      139      ++i;
      140  }
      141 if (NULL == p->pNext || i > pos -1)
      142  {
      143      return false;
      144  }
      145 PNODE q = p->pNext;
      146 *pVal = p->data;
      147 p->pNext=q->pNext;
      148 free(q);
      149 q=NULL;
      150 return true;
      151 }
      152 153 void sort_list(PNODE pHead)
      154 {
      155 int i,j,t;
      156 PNODE p,q;
      157 int len = length_list(pHead);
      158 for(i=0,p=pHead->pNext;i<len-1;i++,p=p->pNext)
      159  {
      160      for(j=i+1,q=p->pNext;j<len;j++,q=q->pNext)
      161      {
      162          if(p->data > q->data)
      163          {
      164              t = p->data;
      165              p->data = q->data;
      166              q->data = t; 
      167          }
      168      }
      169  }
      170 return;
      171

       

  • 線性結構的兩種常見應用之一:棧

    • 定義:一種可以實現“先進後出”的儲存結構

    • 分類:靜態棧;動態棧。

    • 演算法:壓棧;出棧

    • 應用:函式呼叫;中斷;表示式求值;記憶體分配;緩衝處理;迷宮

    示例程式碼

      1 # include <stdio.h>
      2 # include <malloc.h>
      3 # include <stdlib.h>
      4   5 typedef struct Node
      6 {
      7     int data;
      8     struct Node * pNext;
      9 }NODE,* PNODE;
     10  11 typedef struct Stack
     12 {
     13     PNODE pTop;
     14     PNODE pBottom;
     15 }STACK,* PSTACK;
     16  17 //初始化
     18 void init_stack(PSTACK);
     19 //壓棧
     20 void push_stack(PSTACK,int);
     21 //出棧
     22 int pop_stack(PSTACK);
     23 //遍歷棧
     24 void traverse_stack(PSTACK);
     25 //判斷棧是否為空
     26 bool is_empty(PSTACK);
     27 //清空棧
     28 void clear(PSTACK);
     29  30 int main(void)
     31 {
     32     int val;//出棧的值
     33     STACK s;
     34     init_stack(&s);//目的是造出一個空棧
     35     push_stack(&s,1);//壓棧
     36     push_stack(&s,2);
     37     push_stack(&s,3);
     38     push_stack(&s,4);
     39     push_stack(&s,5);
     40     traverse_stack(&s);
     41     pop_stack(&s,&val);
     42     return 0;
     43 }
     44  45 void init_stack(PSTACK pS)
     46 {
     47     pS->pTop=(PNODE)malloc(sizeof(NODE));
     48     if (NULL == pS->pTop)
     49     {
     50         printf("分配記憶體失敗,程式終止!\n");
     51         exit(-1);
     52     }
     53     else 
     54     {
     55         pS->pBottom = pS->pTop;
     56         pS->pTop->pNext = NULL;
     57     }
     58     return;
     59 }
     60  61 void push_stack(PSTACK pS,int val)
     62 {
     63     PNODE pNew = (PNODE)malloc(sizeof(NODE));
     64     if (NULL == pNew)
     65     {
     66         printf("分配記憶體失敗,程式終止!\n");
     67         exit(-1);
     68     }
     69     pNew->data = val;
     70     pNew->pNext = pS->pTop;
     71     pS->pTop = pNew;
     72     
     73     return;
     74 }
     75  76 void traverse_stack(PSTACK pS)
     77 {
     78     
     79     PNODE p = pS->pTop;
     80     while (p != ps->pBottom) 
     81     {
     82         printf("%d\t",p->data);
     83         p = p->pNext;
     84     }
     85     printf("\n");
     86     return;
     87 }
     88  89 bool is_empty(PSTACK pS)
     90 {
     91     if (pS->pTop == pS->pBottom)
     92         return true;
     93     else 
     94         return false;
     95 }
     96  97 bool pop_stack(PSTACK pS,*pVal)
     98 {
     99     if (is_empty(pS)){
    100         return false;
    101     }
    102     else 
    103     {
    104         PNODE p = pS->pTop;
    105         *pVal = p->data;
    106         pS->pTop = p->pNext;
    107         free(p);
    108         p = NULL;
    109         return true;
    110     }
    111 }
    112 113 void clear(PSTACK pS)
    114 {
    115     if(is_empty(pS))
    116         return;
    117     else 
    118     {
    119         PNODE p = pS->pTop;
    120         PNODE q = NULL;
    121         if(p != pS->pBottom)
    122         {
    123 q = p->pNext;
    124 free(p);
    125 p = q;
    126     }
    127 pS->pTop = pS->pBottom;
    128     }
    129 /*此種方法待驗證
    130 while (!is_empty(pS)) 
    131 {
    132 PNODE p = pS->pTop;
    133 pS->pTop = p->pNext;
    134 free(p);
    135 p=NULL;
    136 }
    137 */
    138 return;
    139 }
  • 線性結構的兩種常見應用之二:佇列

    • 定義:一種可以實現“先進先出”的儲存結構

    • 分類

      • 鏈式佇列——用連結串列實現

      • 靜態佇列——用陣列實現

        1. 靜態佇列通常都必須是迴圈佇列

        2. 迴圈佇列的講解

          • 靜態佇列為什麼必須是迴圈佇列

          • 迴圈佇列需要幾個引數來確定

            需要兩個引數來確定佇列,front|rear;

          • 迴圈佇列各個引數的含義

            1. 佇列初始化:front和rear的值都是0

            2. 佇列非空:front代表的是佇列的第一個元素,rear代表的是佇列的最後一個有效元素的下一個位置

            3. 佇列空:front和rear的值相等,但不一定是0

          • 迴圈佇列入隊偽演算法

            1. 將值存入rear所代表的位置

            2. rear = (rear + 1) % 陣列的長度

          • 迴圈隊列出隊偽演算法

            1. front = (front + 1) % 陣列的長度

          • 如何判斷迴圈佇列是否為空

            if (front == rear)

          • 如何判斷迴圈佇列是否已滿

            兩種方式:

            1. if ((rear + 1 % 陣列的長度) == front)

            2. 元素個數=陣列長度-1

        3. 佇列的具體應用:所有和時間有關的操作都與佇列有關

          樣例程式碼

            1 # include <stdio.h>
            2 # include <malloc.h>
            3 # include <stdboo.h>
            4   5 typedef struct Queue
            6 {
            7 int * pBase;//迴圈陣列的首地址
            8 int front;//隊頭
            9 int rear;//隊尾
           10 }QUEUE;
           11  12 //初始化佇列
           13 void init(QUEUE *);
           14 //判斷佇列是否為空
           15 bool is_empty(QUEUE *);
           16 //佇列是否已滿
           17 bool is_full(QUEUE *);
           18 //插入佇列 入隊
           19 bool en_queue(QUEUE *,int val);
           20 //遍歷佇列
           21 void traverse_queue(QUEUE *);
           22 //出隊
           23 bool out_queue(QUEUE *,int *);
           24  25 int main(void)
           26 {
           27  int val;//出隊的元素
           28  QUEUE Q;
           29  init(&Q);
           30  en_queue(&Q,1);
           31  en_queue(&Q,2);
           32  en_queue(&Q,3);
           33  en_queue(&Q,4);
           34  en_queue(&Q,5);
           35  en_queue(&Q,6);
           36  traverse_queue(&Q);
           37  if(out_queue(&Q,&val))
           38  {
           39      printf("The elem out queue is %d .",val);
           40  }
           41  return 0;
           42 }
           43  44 void init(QUEUE * pQ)
           45 {
           46  pQ->pBase = (int *)malloc(sizeof(int) * 6);
           47  pQ->front = 0;
           48  pQ->rear = 0;
           49 }
           50  51 bool is_empty(QUEUE * pQ)
           52 {
           53  return pQ->front == pQ->rear? true:false;
           54 }
           55  56 bool is_full(QUEUE * pQ)
           57 {
           58  return pQ->front == (pQ->rear + 1) % 6 ? true:false;
           59 }
           60  61 //入隊
           62 bool en_queue(QUEUE * pQ,int val)
           63 {
           64  if(is_full(pQ))
           65     return false;
           66  else
           67  {
           68      pQ->pBase[pQ->rear] = val;
           69      pQ->rear = (pQ->rear + 1) % 6;
           70      return true;
           71  }
           72  
           73 }
           74  75 //遍歷佇列
           76 void traverse_queue(QUEUE * pQ)
           77 {
           78     if(is_empty(pQ))
           79  {
           80      printf("The queue is empty!");
           81      return;
           82  }
           83  else
           84  {
           85      int val;
           86      while(pQ->front != pQ->rear)
           87      {
           88          printf("%d ",pQ->pBase[pQ->front]);
           89          pQ->front = (pQ->front + 1) % 6;
           90      }
           91      printf("\n");
           92      return;
           93  }
           94 }    
           95  96 //出隊
           97 bool out_queue(QUEUE * pQ,int *pVal)
           98 {
           99  if(is_empty(pQ))
          100  {
          101      printf("The queue is empty.");
          102      return false;
          103  }
          104  else
          105  {
          106      *pVal = pQ->pBase[pQ->front];
          107      pQ->front = (pQ->front + 1) % 6;
          108      return true;
          109  }
          110

主題:遞迴

定義:一個函式自己直接或間接呼叫自己

遞迴滿足三個條件

  1. 遞迴必須得有一個明確的中止條件

  2. 該函式所處理的資料規模必須在遞減

  3. 這個轉化必須是可解的

迴圈和遞迴

  • 遞迴

    • 易於理解

    • 速度慢

    • 儲存空間大

  • 迴圈

    • 不易理解

    • 速度快

    • 儲存空間小

函式的呼叫:

  • 當在一個函式的執行期間呼叫另一個函式時,在執行被調函式之前,系統需要完成三件事:

    1. 將所有的實際引數、返回地址等資訊傳遞給被調函式儲存。

    2. 為被調函式的區域性變數(也包括行參)分配儲存空間。

    3. 將控制轉移到被調函式的入口。

  • 從被調函式返回函式之前,系統也要完成三件事:

    1. 儲存被調函式的返回結果。

    2. 釋放被調函式所佔的儲存空間。

    3. 依照被調函式儲存的返回地址將控制轉移到呼叫函式。

  • 當有多個函式相互呼叫時,按照”後呼叫先返回“的原則,上述函式之間資訊傳遞和控制轉移必須藉助”棧“來實現,即系統將整個程式執行時所需的資料空間安排在一個棧中,每當呼叫一個函式時,就在棧頂分配一個儲存區,進行壓棧操作,每當一個函式退出時,就釋放它的儲存區,就做出棧操作,當前執行的函式永遠都在棧頂位置。

  • A函式呼叫A函式和A函式呼叫B函式在計算機看來是沒有任何區別的,只不過用我們日常的思維方式理解比較怪異而已!

  1. 1+2+3+...+100的和


    程式碼實現
     1 # include <stdio.h>
     2  3 int sum(int);
     4  5 int main(void)
     6 {
     7     int n = 100;
     8     printf("%d\n",sum(n));
     9  return 0;
    10 }
    11 12 int sum(int n)
    13 {
    14  if(1 == n)
    15      return 1;
    16  else
    17      return n + sum(n-1);
    18
  2. 求階乘

    程式碼實現
     1 # include <stdio.h>
     2  3 long multi(int);
     4  5 int main(void)
     6 {
     7     printf("%ld\n",multi(6));
     8  return 0;
     9 } 
    10 11 long multi(int n)
    12 {
    13  if (1 == n)
    14     return 1;
    15  else
    16     return n * multi(n - 1);
    17 }
  3. 漢諾塔

    虛擬碼:

    如果只有一個盤子:

    直接將盤子從A柱子移到C柱子

    否則:

    先將A柱子上的n-1個盤子藉助C柱子移到B柱子

    再直接將盤子從A柱子移到C柱子

    最後再將B柱子上的盤子藉助A柱子移到C柱子上

    程式碼實現樣例:

     1 # include <stdio.h>
     2  3 void hanoi(int,char,char,char);
     4  5 int main(void)
     6 {
     7     //柱子編號
     8     char ch1 = 'A';
     9     char ch2 = 'B';
    10     char ch3 = 'C';
    11     //盤子數量
    12     int n;
    13     printf("請輸入盤子的數量");
    14     scanf("%d",&n);
    15     hanoi(n,'A','B','C');
    16     
    17     return 0;
    18 }
    19 20 void hanoi(int n,char 'A',char 'B',char 'C')
    21 {
    22     if (1 == n)
    23         printf("編號為%d的盤子:%c-->%c\n",n,'A',"C");
    24     else
    25     {
    26         hanoi(n-1,A,C,B);
    27         printf("編號為%d的盤子:%c-->%c\n",n,'A',"C");
    28         hanoi(n-1,B,A,C);
    29     }
    30
  4. 走迷宮

    A*(A-Star)演算法實現

遞迴的應用
  • 樹和森林就是以遞迴的方式定義的

  • 樹和圖的很多演算法

  • 很多數學公式:例如斐波拉契數列

非線性結構

樹的定義
  • 專業定義:

    1. 有且只有一個稱為根的節點

    2. 有若干個互不相交的子樹,這些子樹本身也是一顆樹

  • 通俗定義:

    1. 樹是由節點和邊組成

    2. 每個節點只有一個父節點但可以有多個子節點

    3. 但有一個節點例外,該節點沒有父節點,此節點稱為根節點

  • 專業術語:

    • 節點,父節點,子節點

    • 子孫,堂兄弟

    • 深度:從根節點到最底層節點的層數稱之為深度,根節點是第一層

    • 葉子節點:沒有子節點的節點

    • 非終端節點:實際就是非葉子節點

    • 度:子節點的個數

樹的分類
  • 一般樹:任意一個節點的子節點的個數都不受限制

  • 二叉樹:任意一個節點的子節點個數最多兩個,且子節點的位置不可更改

    • 分類

      • 一般二叉樹

      • 滿二叉樹:在不增加樹層數的前提下,無法再多新增一個節點的二叉樹就是滿二叉樹

      • 完全二叉樹:如果只是刪除了滿二叉樹最底層最右邊的連續若干個節點,這樣形成的二叉樹就是完全二叉樹。(滿二叉樹是完全二叉樹的一個特例)

  • 森林:n個互不相交的樹的集合

樹的儲存
  • 二叉樹的儲存

    • 連續儲存【完全二叉樹】

      優點:查詢某個節點的父節點和子節點(也包括判斷有沒有子節點)

      缺點:耗用記憶體空間過大

    • 鏈式儲存

  • 一般樹的儲存

    • 雙親表示法:求父節點方便

    • 孩子表示法:求子節點方便

    • 雙親孩子表示法:求父節點和子節點都很方便

    • 二叉樹表示法:把一個普通樹轉化成二叉樹來儲存

      具體轉換方法:

      設法保證任意一個節點的左指標域指向它的第一個孩子,右指標域指向它的兄弟,只要滿足此條件,就可以把一個普通樹轉化為二叉樹。

      一個普通樹轉化成的二叉樹一定沒有右子樹

  • 森林的儲存

    先把森林轉化為二叉樹,再儲存二叉樹:

    將相鄰的父節點依次作為節點的右子樹再對各父節點進行轉化

樹的操作
  • 遍歷

    • 先序遍歷【先訪問根節點】

      1. 先訪問根節點

      2. 再先序訪問左子樹

      3. 最後先序訪問右子樹

        例子:

                A
          / \
        B   C
            / \   \
        D   E   F
          / \   \ / \
        G   H   I J k

        先序遍歷結果:ABDGHEICFJK

    • 中序遍歷【中間訪問根節點】

      1. 中序遍歷左子樹

      2. 再訪問根節點

      3. 再中序遍歷右子樹

        例子:

                A
          / \
        B   C
            / \   \
        D   E   F
          / \   \ / \
        G   H   I J k

        中序遍歷結果:GDHBEIACJFK

    • 後續遍歷【最後訪問根節點】

      1. 先中序遍歷左子樹

      2. 再中序遍歷右子樹

      3. 最後遍歷根節點

        例子:

                A
          / \
        B   C
            / \   \
        D   E   F
          / \   \ / \
        G   H   I J k

        後序遍歷結果:GHDIEBJKFCA

  • 已知兩種遍歷序列求原始二叉樹

    通過先序和中序或者中序和後序我們可以還原出原始二叉樹,但是通過先序和後序是無法還原出原始二叉樹;

    換句話,只有通過先序和中序或者中序和後序,我們才可以唯一的確定一個二叉樹。

    例子1:

    先序:ABCDEFGH
    中序:BDCEAFHG
    求後序?
    分析:按照先序的定義,A為最外層根節點,按照中序的定義和前面的結論可知BDCE為A節點的左子樹節點,FHG為A節點的右子樹,再依次按照兩個遍歷定義可以推出原始二叉樹為:
    A
      / \
    B   F
    \ \
    C G
    / \ /
      D E H
    那麼此二叉樹的後序為:DECBHGFA

    例子2:

    先序:ABDGHCEFI
    中序:GDHBAECIF
    求後序?
    分析:按照先序的定義得到A為最外層根節點,再根據中序結果可知GDHB為A的左子樹,ECIF為A的右子樹;B先出現在先序結果中可知B為左子樹的根節點,再根據中序結果知B節點沒有右子樹,GDH均為B節點的左子樹,再根據先序結果D先出現,知D為B左子樹的根節點,再根據先序發現G在D的後面且中序中G在D的前面得出G為D左子樹的根節點,那麼D的右子樹的根節點就是H了,依次類推A的右子樹,得出原始二叉樹為:
        A
        / \
      B   C
      /   / \
    D   E   F
    / \     /
    G   H   I
    那麼此二叉樹的後序為:GHDBEIFCA

    例子3:

    中序:BDCEAFHG
    後序:DECBHGFA
    求先序?
    分析:由後序結果知A為最外層根節點,再根據中序結果知BDCE為A節點的左子樹,FHG為A的右子樹;A的左子樹中B最靠近A那麼根據後序規則得出B為左子樹的根節點,再根據中序結果B在結果的第一位,由中序規則知B沒有左子樹,DCE均為B的右子樹,在DCE中後序結果C最靠近B,得出C為B的右子樹的根節點,再依據中序結果知C前面是D後面是E得出D為C的左子樹,E為C的右子樹,同理可以推出A的右子樹,得出原始二叉樹為:
    A
      / \
    B   F
      \   \
      C   G
      / \ /
    D   E H
    那麼此二叉樹的先序為:ABCDEFGH
  • 二叉樹的程式碼實現舉例

      1 # include <stdio.h>
      2 # include <malloc.h>
      3   4 typedef struct BinaryTree
      5 {
      6     char data;//節點值 資料域
      7     struct BinaryTree * pLchild;//左子樹 左指標域
      8     struct BinaryTree * pRchild;//右子樹 右指標域
      9 }BINARYTREE,* PBINARYTREE;
     10  11 //建立一個靜態二叉樹
     12 PBINARYTREE create_binary_tree(void);
     13 //先序遍歷
     14 void pre_traversal(PBINARYTREE);
     15 //中序遍歷
     16 void in_traversal(PBINARYTREE);
     17 //後序遍歷
     18 void post_traversal(PBINARYTREE);
     19  20 int main(void)
     21 {
     22     PBINARYTREE pT = NULL;
     23     pT = create_binary_tree();
     24     printf("先序遍歷結果為:");
     25     pre_traversal(pT);
     26     printf("\n中序遍歷結果為:");
     27     in_traversal(pT);
     28     printf("\n後序遍歷結果為:");
     29     post_traversal(pT);
     30     return 0;
     31 }
     32  33 /**建立以下結果的二叉樹,此二叉樹結構是上面二叉樹遍歷的例子,可對比結果是否正確
     34         A
     35        / \
     36       B   C
     37      / \   \
     38     D   E    F
     39    / \   \  / \
     40   G   H   I J  k
     41 */
     42 PBINARYTREE create_binary_tree(void)
     43 {
     44     //為每個二叉樹節點申請記憶體空間
     45     PBINARYTREE pA = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     46     PBINARYTREE pB = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     47     PBINARYTREE pC = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     48     PBINARYTREE pD = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     49     PBINARYTREE pE = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     50     PBINARYTREE pF = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     51     PBINARYTREE pG = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     52     PBINARYTREE pH = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     53     PBINARYTREE pI = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     54     PBINARYTREE pJ = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     55     PBINARYTREE pK = (PBINARYTREE)malloc(sizeof(BINARYTREE));
     56     //給每個二叉樹節點的資料域賦值
     57     pA->data = 'A';
     58     pB->data = 'B';
     59     pC->data = 'C';
     60     pD->data = 'D';
     61     pE->data = 'E';
     62     pF->data = 'F';
     63     pG->data = 'G';
     64     pH->data = 'H';
     65     pI->data = 'I';
     66     pJ->data = 'J';
     67     pK->data = 'K';
     68     //給每個二叉樹節點的左右指標域賦值
     69     pA->pLchild = pB; 
     70     pA->pRchild = pC; 
     71     pB->pLchild = pD; 
     72     pB->pRchild = pE;
     73     pC->pLchild = NULL; 
     74     pC->pRchild = pF;
     75     pD->pLchild = pG; 
     76     pD->pRchild = pH;
     77     pE->pLchild = NULL; 
     78     pE->pRchild = pI;
     79     pF->pLchild = pJ; 
     80     pF->pRchild = pK;
     81     pG->pLchild = pG->pRchild = NULL;
     82     pH->pLchild = pH->pRchild = NULL;
     83     pI->pLchild = pI->pRchild = NULL;
     84     pJ->pLchild = pJ->pRchild = NULL;
     85     pK->pLchild = pK->pRchild = NULL;
     86     
     87     return pA;
     88 }
     89  90 void pre_traversal(PBINARYTREE pT)
     91 {
     92     if(NULL != pT)
     93     {
     94         printf("%c\t",pT->data);
     95         if(NULL != pT->pLchild)
     96             pre_traversal(pT->pLchild);
     97 if(NULL != pT->pRchild)
     98 pre_traversal(pT->pRchild);
     99     }
    100 return;
    101 }
    102 103 void in_traversal(PBINARYTREE pT)
    104 {
    105 if(NULL != pT)
    106     {
    107 if(NULL != pT->pLchild)
    108 in_traversal(pT->pLchild);
    109 printf("%c\t",pT->data);
    110 if(NULL != pT->pRchild)
    111 in_traversal(pT->pRchild);
    112     }
    113 return;
    114 }
    115 116 void post_traversal(PBINARYTREE pT)
    117 {
    118 if(NULL != pT)
    119     {
    120 if(NULL != pT->pLchild)
    121 post_traversal(pT->pLchild);
    122 if(NULL != pT->pRchild)
    123 post_traversal(pT->pRchild);
    124 printf("%c\t",pT->data);
    125     }
    126 return;
    127 }

     

樹的應用
  • 樹是資料庫中資料組織的一種重要形式

  • 作業系統子父程序的關係本身就是一個樹

  • 面嚮物件語言中類的繼承關係

  • 赫夫曼樹

視訊中沒有具體講解

查詢和排序

查詢:折半查詢
排序

關於排序演算法,推薦去中文維基百科上面檢視,有動態排序過程,有助於理解演算法本質!

排序演算法

  1. 氣泡排序

     1 //簡單氣泡排序舉例【升序】
     2  3 # include <stdio.h>
     4  5 void bubble_sort(int *,int);
     6  7 int main(void)
     8 {
     9     int i;
    10     int len = 6;
    11     int arr[len] = {2,10,8,5,3,1};
    12     
    13     bubble_sort(arr,len);
    14     for(i = 0;i < 6;i++)
    15         printf("%d ",arr[i]);
    16     printf("\n");
    17     return 0;
    18 }
    19 20 void bubble_sort(int * arr,int len)
    21 {
    22     int i,j,t;
    23     for(i = 0; i < len; i++)
    24     {
    25         for(j = i+1; j < len-1; j++)
    26         {
    27             if(arr[i] > arr[j])
    28             {
    29                 t = arr[i];
    30                 arr[i] = arr[j];
    31                 arr[j] = t;
    32             }
    33         }
    34     }
    35 }
  2. 插入排序

     1 //直接插入排序【升序】
     2  3 # include <stdio.h>
     4  5 void insertion_sort(int *,int);
     6  7 int main(void)
     8 {
     9     int i;
    10     int len = 6;//陣列長度
    11     int arr[len] = {2,10,8,5,3,1};
    12     insertion_sort(arr,len);
    13     for(i = 0;i < 6;i++)
    14         printf("%d ",arr[i]);
    15     printf("\n");
    16 }
    17 18 void insertion_sort(int * arr,int len)
    19 {
    20     int i,j,t;
    21     for (i=1; i<len; i++)
    22     {
    23         if(arr[i]<arr[i-1])
    24         {
    25             t = arr[i];
    26             j = i - 1;
    27             for(j; j>=0 && arr[j]>t;j--)
    28             {
    29                 arr[j+1] = arr[j];
    30             }
    31             a[j+1] = t;
    32         }
    33     }
    34 }
  3. 選擇排序

     1 //直接選擇排序【升序】
     2  3 # include <stdio.h>
     4  5 void selection_sort(int *,int);
     6  7 int main(void)
     8 {
     9     int i;
    10     int len = 6;//陣列長度
    11     int arr[6] = {2,10,8,5,3,1};
    12     selection_sort(arr,len);
    13     for(i = 0;i < 6;i++)
    14         printf("%d ",arr[i]);
    15     printf("\n");
    16     return 0;
    17 }
    18 19 void selection_sort(int * arr,int len)
    20 {
    21     int i,j,t,min;
    22     for(i=0; i<len-1; i++)
    23     {
    24         for(min=i,j=i+1; j<len; j++)
    25         {
    26             if(arr[min] < arr[j])
    27                 min = j;         
    28         }
    29         if(min != j)
    30         {
    31             t = arr[i];
    32             arr[i] = arr[min];
    33             arr[min] = t;
    34          }
    35     }
    36 }

     

  4. 快速排序

     1 # include <stdio.h>
     2  3 void quick_sort(int *,int,int);
     4 int find_pos(int *,int,int);
     5  6 int main(void)
     7 {
     8     int i,len,low,high;
     9     low = 0;
    10     high = 6;
    11     int arr[7] = {13,2,1,3,8,5,1};
    12     quick_sort(arr,low,high);//low表示起始位置,high表示結束位置
    13     for(i = 0;i < 7;i++)
    14         printf("%d ",arr[i]);
    15     printf("\n");
    16     return 0;
    17 }
    18 19 void quick_sort(int * arr,int low,int high)
    20 {
    21     int pos;
    22     if (low < high)
    23     {
    24         pos = find_pos(arr,low,high);
    25         quick_sort(arr,low,pos-1);
    26         quick_sort(arr,pos+1,high);
    27     }
    28 }
    29 30 int find_pos(int * arr,int low,int high)
    31 {
    32     int val = arr[low];
    33     while(low < high)
    34     {
    35         while(low < high && arr[high] >= val)
    36             --high;
    37         arr[low] = arr[high];
    38         while(low < high && arr[low] <= val)
    39             ++low;
    40         arr[high] = arr[low];
    41     }
    42     arr[low] = val;
    43     return high;
    44 }

     

  5. 歸併排序

    摘自中文維基百科-歸併排序

     1 # include <stdio.h>
     2  3 void merge_sort(int arr[], const int len);
     4 void merge_sort_recursive(int arr[], int reg[], int start, int end);
     5  6 int main(void)
     7 {
     8     int i;
     9     int arr[7] = {13,2,1,3,8,5,1};
    10     merge_sort(arr,7);
    11     for(i = 0;i < 7;i++)
    12         printf("%d ",arr[i]);
    13     printf("\n");
    14     return 0;
    15 }
    16 17 void merge_sort_recursive(int arr[], int reg[], int start, int end) {
    18     if (start >= end)
    19         return;
    20     int len = end - start, mid = (len >> 1) + start;
    21     int start1 = start, end1 = mid;
    22     int start2 = mid + 1, end2 = end;
    23     merge_sort_recursive(arr, reg, start1, end1);
    24     merge_sort_recursive(arr, reg, start2, end2);
    25     int k = start;
    26     while (start1 <= end1 && start2 <= end2)
    27         reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
    28     while (start1 <= end1)
    29         reg[k++] = arr[start1++];
    30     while (start2 <= end2)
    31         reg[k++] = arr[start2++];
    32     for (k = start; k <= end; k++)
    33         arr[k] = reg[k];
    34 }
    35 36 void merge_sort(int arr[], const int len) {
    37     int reg[len];
    38     merge_sort_recursive(arr, reg, 0, len - 1);
    39

總結

堅持就是勝利!