C語言之高階資料講解

上善若泪發表於2024-08-11

目錄
  • 1 高階資料
    • 1.1 結構
    • 1.2 從陣列到連結串列
    • 1.3 抽象資料型別(ADT)
      • 1.3.1 講解
      • 1.3.2 實踐
    • 1.4 佇列ADT
      • 1.4.1 講解
      • 1.4.2 用佇列進行模擬
    • 1.5 連結串列和陣列
    • 1.6 二叉查詢樹
      • 1.6.1 講解
      • 1.6.2 實踐

1 高階資料

1.1 結構

在開始編寫程式碼之前,要做很多程式設計方面的決定。
陣列表示相對不靈活,在執行時確定所需記憶體量會更好。
假設要編寫一個程式,讓使用者輸入一年內看過的電影,儲存影片的資訊。可以使用結構儲存電影,用結構陣列儲存多部電影。但給陣列分配空間時,會出現分配空間過大浪費或者分配空間過小不夠用的問題。使用動態記憶體(malloc)分配可以解決這個問題。

示例程式:

// films1.c -- 使用一個結構陣列 
#include <stdio.h>
#include <string.h>

#define TSIZE 45 // 儲存片名的陣列大小
#define FMAX 5   // 影片的最大數量

struct film
{
    char title[TSIZE];
    int rating;
};

char *s_gets(char str[], int lim);

int main(void)
{
    struct film movies[FMAX];
    int i = 0;
    int j;
    puts("Enter first movie title:");
    while (i < FMAX && s_gets(movies[i].title, TSIZE) != NULL && movies[i].title[0] != '\0')
    {
        puts("Enter your rating <0-10>:");
        scanf("%d", &movies[i++].rating);
        while (getchar() != '\n')
            continue;
        puts("Enter next movie title (empty line to stop):");
    }

    if (i == 0)
        printf("No data entered. ");
    else
        printf("Here is the movie list:\n");

    for (j = 0; j < i; j++)
        printf("Movies: %s Rating: %d\n", movies[j].title, movies[j].rating);
    printf("Bye!\n");

    return 0;
}

char *s_gets(char *st, int n)
{
    char *ret_val;
    char *find;
    ret_val = fgets(st, n, stdin);
    if (ret_val)
    {
        find = strchr(st, '\n'); // 查詢換行符
        if (find)                // 如果地址不是NULL
            *find = '\0';        // 在此處放置一個空字元 
        else
            while (getchar() != '\n')
                continue; // 處理輸入行的剩餘字元 
    }
    return ret_val;
}

點選瞭解更多關於結構資訊

1.2 從陣列到連結串列

結構宣告中不能有與本身型別相同的結構,但是可以有指向同型別結構的指標。
連結串列是由一系列結構體構成,每個結構體都有一個指標,該指標指向下一個結構。最後一個成員中此指標的值是0。
為了訪問連結串列,需要一個單獨的指標儲存第一個成員的地址。
把使用者介面和程式碼細節分開的程式更容易理解和更新。

示例程式:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define TSIZE 45 //片名大小

struct film {
  char title[TSIZE];
  int rating;
  struct film * next; //指向連結串列的下一個結構
};

char * s_gets(char * st, int n);


int main(void)
{
  struct film * head = NULL;
  struct film * prev = NULL, *current = NULL;
  char input[TSIZE];

  puts("輸入第一部電影的名字:");
  while (s_gets(input, TSIZE) != NULL && input[0] != '\0')
  {
    current = (struct film *) malloc(sizeof(struct film));
    if (head == NULL)
      head = current;
    else
      prev->next = current;
    current->next = NULL;
    strcpy(current->title, input);
    puts("輸入評分<0-10>:");
    scanf("%d", &current->rating);
    while (getchar() != '\n')
      continue;
    puts("輸入下一部電影名字(直接回車可退出)");
    prev = current;

  }
  //顯示電影
  if (head == NULL)
    printf("無資料.");
  else
  {
    printf("電影列表如下:\n");
    current = head;
    while (current != NULL)
    {
      printf("電影:%s 評分:%d\n", current->title, current->rating);
      current = current->next;
    }
  }

  //釋放記憶體
  current = head;
  while (head != NULL)  //此處和書不同,書上執行出錯。我認為這裡應該判斷head是否NULL而不是current是否為NULL
  {
    current = head;
    head =head->next;

    free(current);
  }
  printf("BYE\n");

  return 0;
}

char * s_gets(char * st, int n)
{
  char * ret_val;
  char * find;
  ret_val = fgets(st, n, stdin);
  if (ret_val)
  {
    find = strchr(st, '\n');//查詢換行符
    if (find)
      *find = '\0'; //將換行符換成'\0'
    else
      while (getchar() != '\n')  //處理輸入行剩餘的字元
        continue;
  }
  return ret_val;
}

1.3 抽象資料型別(ADT)

1.3.1 講解

型別特指兩種資訊:屬性操作。要定義一個新的資料型別,就必須提供儲存資料的方法,還有操控資料的方法。
定義新型別的好方法是:先提供型別屬性和相關操作的抽象描述。這些描述不依賴特定的實現,也不依賴特定的程式語言,稱為抽象資料型別(ADT)。再開發一個實現ADT的程式設計介面,指明如何儲存資料和執行所需操作的函式。最後編寫程式碼實現介面。

C語言中通常的做法是,把型別定義和函式原型放在一個標頭檔案中,該標頭檔案提供資訊。實現介面需要一個原始檔,記錄需要函式的細節。程式由標頭檔案、包含處理此型別函式的原始檔和主幹操作的原始檔組成。

對於大型專案而言,把實現和最終介面隔離的做法相當有用。

定義新型別的好方法:

  • 提供型別屬性和相關操作的抽象描述。這些描述即不能依賴特定的實現,也不能依賴特定的程式語言。這種正式的抽象描述被稱為抽象資料型別(ADT)。
  • 開發一個實現 ADT 的程式設計介面。即指明如何儲存資料和執行所需操作的函式。
  • 編寫程式碼實現介面。

1.3.2 實踐

下面是連結串列的具體實現:

//list.h
#pragma once 
#include<stdbool.h>

/*特定程式的宣告*/
#define TSIZE 45 //儲存電影名的陣列大小
struct film
{
  char title[TSIZE];
  int rating;
};


/*一般型別定義*/

typedef struct film Item;

typedef struct node
{
  Item item;
  struct node * next;
}Node;
typedef Node * List;

/*函式原型*/

/*操作:   初始化一個連結串列   */
/*前提條件: plist指向一個連結串列 */
/*後置條件: 連結串列初始化為空   */
void InitializeList(List * plist);

/*操作:   確定連結串列是否為空定義,plist指向一個已初始化的連結串列 */
/*後置條件: 如果連結串列為空,返回ture;否則返回false       */
bool ListIsEmpty(const List * plist);

/*操作:   確定連結串列是否已滿,plist指向一個已初始化的連結串列   */
/*後置條件: 如果連結串列已滿,返回true;否則返回false       */
bool ListIsFull(const List * plist);

/*操作:   確定連結串列中的項數,plist指向一個已初始化的連結串列   */
/*後置條件: 返回連結串列中的項數                  */
unsigned int ListItemCount(const List *plist);

/*操作:   在連結串列的末尾新增項                   */
/*前提條件: item是一個待新增至連結串列的項,plist指向一個已初始化的連結串列  */
/*後置條件: 如果可以,執行新增操作,返回true;否則返回false      */
bool AddItem(Item item, List * plist);

/*操作:   把函式作用於連結串列的每一項                */
/*        plist指向一個已初始化的連結串列                */
/*        pfun指向一個函式,該函式接受一個Item型別引數,無返回值 */  
/*後置條件: pfun指向的函式作用於連結串列的每一項一次          */
void Traverse(const List*plist, void(*pfun)(Item item));

/*操作:   釋放已分配的記憶體(如果有的話)             */
/*        plist指向一個已初始化的連結串列                */
/*後置條件: 釋放為連結串列分配的記憶體,連結串列設定為空           */
void EmptyTheList(List * plist);
//list.c
#include<stdio.h>
#include<stdlib.h>
#include"list.h"

static void CopyToNode(Item item, Node * pnode);

void InitializeList(List * plist)
{
  *plist = NULL;
}

bool ListIsEmpty(const List * plist)
{
  if (*plist == NULL)
    return true;
  else
    return  false;
}

bool ListIsFull(const List * plist)
{
  Node * pt;
  bool full;
  pt = (Node *)malloc(sizeof(Node));
  if (pt == NULL)
    full = true;
  else
    full = false;
  free(pt);
  return full;
}

unsigned int ListItemCount(const List * plist)
{
  unsigned int count = 0;
  Node * pnode = *plist;
  while (pnode != NULL)
  {
    ++count;
    pnode = pnode->next;
  }
  return count;
}

bool AddItem(Item item, List * plist)
{
  Node * pnew;
  Node * scan = *plist;
  pnew = (Node *)malloc(sizeof(Node));
  if (pnew == NULL)
    return false;
  CopyToNode(item, pnew);
  pnew->next = NULL;
  if (scan == NULL)
    *plist = pnew;
  else
  {
    while (scan->next != NULL)
      scan = scan->next;
    scan->next = pnew;
  }

  return true;
}

void Traverse(const List * plist, void(*pfun)(Item item)) 
{
  Node * pnode = *plist;
  while (pnode!= NULL)
  {
    (*pfun)(pnode->item);
    pnode = pnode->next;
  }
}

void EmptyTheList(List * plist)
{
  Node * psave;
  while (*plist != NULL)
  {
    psave = (*plist)->next;
    free(*plist);
    *plist = psave;
  }
}
static void CopyToNode(Item item, Node * pnode)
{
  pnode->item = item;
}

示例程式:

/*film3.c            */
/*與list.c一起編譯        */
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"list.h"
void showMovies(Item item);
char * s_gets(char * st, int n);
int main(void)
{
  List movies;
  Item temp;
  /*初始化 */
  InitializeList(&movies);
  if (ListIsFull(&movies))
  {
    fprintf(stderr, "無可用記憶體,告辭。\n");
    exit(1);
  }

  /*獲取使用者輸入 並儲存*/
  puts("輸入第一個電影名稱:");
  while (s_gets(temp.title, TSIZE) != NULL && temp.title[0] != '\0')
  {
    puts("輸入你的評分<0-10>:");
    scanf("%d", &temp.rating);
    while (getchar() != '\n')
      continue;
    if (AddItem(temp, &movies) == false)
    {
      fprintf(stderr, "分配記憶體出錯\n");
      break;
    }
    if (ListIsFull(&movies))
    {
      puts("列表滿了.");
      break;
    }
    puts("輸入下一步電影名稱(回車結束程式)");
  }

  /*顯示*/
  if (ListIsEmpty(&movies))
    printf("列表為空");
  else 
  {
    printf("Here is the movie list:\n");
    Traverse(&movies, showMovies);
  }
  printf("你輸入了%d個電影\n", ListItemCount(&movies));

  /*清理*/
  EmptyTheList(&movies);
  printf("再見\n");

  return 0;
}

void showMovies(Item item)
{
  printf("Movie: %s Rating: %d\n", item.title, item.rating);
}

char * s_gets(char * st, int n)
{
  char * ret_val;
  char * find;
  ret_val = fgets(st, n, stdin);
  if (ret_val)
  {
    find = strchr(st, '\n');//查詢換行符
    if (find)
      *find = '\0'; //將換行符換成'\0'
    else
      while (getchar() != '\n')  //處理輸入行剩餘的字元
        continue;
  }
  return ret_val;
}

1.4 佇列ADT

1.4.1 講解

佇列是具有一些特殊屬性的連結串列,新項只能新增到連結串列的末尾,只能從連結串列的開頭移除項。佇列先進先出。

1.4.2 用佇列進行模擬

佇列特性:先進先出。

示例程式:

// mall.c -- 使用Queue介面
// 和queue.c一起編譯
#include <stdio.h>
#include <stdlib.h>     // 提供rand()和srand()的原型 
#include <time.h>       // 提供time()的原型 
#include "17_6_queue.h" // 更改Item的typedef 
#define MIN_PER_HR 60.0

bool newcustomer(double x);   // 是否有新顧客到來?
Item customertime(long when); // 設定顧客引數

int main(void)
{

    Queue line;             // 新的顧客資料 
    Item temp;              // 模擬的小時數 
    int hours;              // 每小時平均多少位顧客 
    int perhour;            // 每小時平均多少位顧客
    long cycle, cyclelimit; // 迴圈計數器、計數器的上限
    long turnaways = 0;     // 因佇列已滿被拒的顧客數量 
    long customers = 0;     // 加入佇列的顧客數量
    long served = 0;        // 在模擬期間諮詢過Sigmund的顧客數量 
    long sum_line = 0;      // 累計的佇列總長 
    long wait_time = 0;     // 從當前到Sigmund空閒所需的時間 
    double min_per_cust;    // 顧客到來的平均時間 
    long line_wait = 0;     // 佇列累計的等待時間 

    InitializeQueue(&line);
    srand((unsigned int)time(0)); // rand()隨機初始化
    puts("Case Study: Sigmund Lander's Advice Booth");
    puts("Enter the number of simulation hours:");
    scanf("%d", &hours);
    cyclelimit = MIN_PER_HR * hours;
    puts("Enter the average number of customers per hour:");
    scanf("%d", &perhour);
    min_per_cust = MIN_PER_HR / perhour;
    for (cycle = 0; cycle < cyclelimit; cycle++)
    {
        if (newcustomer(min_per_cust))
        {
            if (QueueIsFull(&line))
                turnaways++;
            else
            {
                customers++;
                temp = customertime(cycle);
                EnQueue(temp, &line);
            }
        }
        if (wait_time <= 0 && !QueueIsEmpty(&line))
        {
            DeQueue(&temp, &line);
            wait_time = temp.processtime;
            line_wait += cycle - temp.arrive;
            served++;
        }
        if (wait_time > 0)
            wait_time--;
        sum_line += QueueItemCount(&line);
    }
    if (customers > 0)
    {
        printf("customers accepted: %ld\n", customers);
        printf("  customers served: %ld\n", served);
        printf("         turnaways: %ld\n", turnaways);
        printf("average queue size: %.2f\n", (double)sum_line / cyclelimit);
        printf(" average wait time: %.2f minutes\n", (double)line_wait / served);
    }
    else
        puts("No customers!");
    EmptyTheQueue(&line);

    return 0;
}
// x是顧客到來的平均時間(單位:分鐘)
// 如果1分鐘內有顧客到來,則返回true
bool newcustomer(double x)
{
    if (rand() * x / RAND_MAX < 1)
        return true;
    else
        return false;
}
// when是顧客到來的時間
// 該函式返回一個Item結構,該顧客到達的時間設定為when
// 諮詢時間設定為1~3的隨機值
Item customertime(long when)
{
    Item cust;
    cust.processtime = rand() % 3 + 1;
    cust.arrive = when;
    return cust;
}

1.5 連結串列和陣列

陣列是C語言直接支援的,可以隨機訪問,但是陣列在編譯時就確定大小,插入和刪除元素很麻煩。連結串列執行時確定大小,插入刪除很方便,但是不能隨機訪問,開發難度大。

對於一個排序的列表,二分查詢的效率比順序查詢要高得多。二分查詢把所有元素分為一半,比中間的小就去前半部分,比中間元素大就去後半部分,與中間的相等就算找到了,進入前半或後半部分後以此類推。

如果經常使用增刪操作,使用連結串列更好。如果經常查詢,陣列更好。

陣列和連結串列優缺點:

資料形式 優點 缺點
陣列 C直接支援;提供隨機訪問 在編譯時確定大小;插入和刪除元素時很費時
連結串列 執行時確定大小;快速插入和刪除元素 不能隨機訪問;使用者必須提供程式設計支援

1.6 二叉查詢樹

1.6.1 講解

二叉樹的每個節點有兩個指標,這兩個指標指向其他節點(分別稱為左節點右節點
一般左節點在的項在父節點前面,右節點的項在父節點後面。如果一側沒有子節點,則指向這一側的指標為NULL。二叉樹的頂端稱為根。一個節點和它的所有節點構成子樹。
用二叉樹每次查詢就會排除一半的節點,效率高,但是更復雜。

1.6.2 實踐

// tree.h -- 二叉查詢樹

// 樹種不允許有重複的項

#ifndef _TREE_H_

#define _TREE_H_

#include <stdbool.h>

// 根據具體情況重新定義Item

#define SLEN 20

typedef struct item
{

    char petname[SLEN];

    char petkind[SLEN];

} Item;

#define MAXITEMS 10

typedef struct trnode

{

    Item item;

    struct trnode *left; // 指向左分支的指標

    struct trnode *right; // 指向右分支的指標

} Trnode;

typedef struct tree

{

    Trnode *root; // 指向根節點的指標

    int size; // 樹的項數

} Tree;

// 函式原型

// 操作: 把樹初始化為空

// 前提條件: ptree指向一個樹

// 後置條件: 樹被初始化為空

void InitializeTree(Tree *ptree);

// 操作: 確定樹是否為空

// 前提條件: ptree指向一個樹

// 後置條件: 如果樹為空,該函式返回true,否則返回false

bool TreeIsEmpty(const Tree *ptree);

// 操作: 確定樹是否已滿

// 前提條件: ptree指向一個樹

// 後置條件: 如果樹已滿,該函式返回true,否則返回false

bool TreeIsFull(const Tree *ptree);

// 操作: 確定樹的項數

// 前提條件: ptree指向一個樹

// 後置條件: 返回樹的項數

int TreeItemCount(const Tree *ptree);

// 操作: 在樹中新增一個項

// 前提條件: pi是待新增項的地址,ptree指向一個一初始化的樹

// 後置條件: 如果可以新增,該函式將在樹中新增一個項並返回true,否則返回false

bool AddItem(const Item *pi, Tree *ptree);

// 操作: 在樹中查詢一個項

// 前提條件: pi指向一個項,ptree指向一個已初始化的樹

// 後置條件: 如果在樹中新增一個項,該函式返回true,否則返回false

bool InTree(const Item *pi, const Tree *ptree);

// 操作: 從樹中刪除一個項

// 前提條件: pi是刪除項的地址,ptree指向一個已初始化的樹

// 後置條件: 如果從樹中成功刪除一格項,該函式返回true,否則返回false

bool DeleteItem(const Item *pi, Tree *ptree);

// 操作: 把函式應用到樹中的每一項

// 前提條件: ptree指向一個樹,pfun指向一個函式,該函式接收一個Item型別的引數,並無返回值

// 後置條件: pfun咋想的這個函式為樹中的每一項執行一次

void Traverse(const Tree *ptree, void (*pfun)(Item item));

// 操作: 刪除樹中的所有內容

// 前提條件: ptree指向一個已初始化的樹

// 後置條件: 樹為空

void DeleteAll(Tree *ptree);
// tree.c -- 樹的支援函式
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "17_10_tree.h"
// 區域性資料型別
typedef struct pair
{
    Trnode *parent;
    Trnode *child;
} Pair;

// 區域性函式的原型
static Trnode *MakeNode(const Item *pi);
static bool ToLeft(const Item *i1, const Item *i2);
static bool ToRight(const Item *i1, const Item *i2);
static void AddNode(Trnode *new_node, Trnode *root);
static void InOrder(const Trnode *root, void (*pfun)(Item item));
static Pair SeekItem(const Item *pi, const Tree *ptree);
static void DeleteNode(Trnode **ptr);
static void DeleteAllNodes(Trnode *ptr);

// 函式定義
void InitializeTree(Tree *ptree)
{
    ptree->root = NULL;
    ptree->size = 0;
}

bool TreeIsEmpty(const Tree *ptree)
{
    if (ptree->root == NULL)
        return true;
    else
        return false;
}

bool TreeIsFull(const Tree *ptree)
{
    if (ptree->root == NULL)
        return true;
    else
        return false;
}

int TreeItemCount(const Tree *ptree)
{
    if (ptree->size == MAXITEMS)
        return true;
    else
        return false;
}

bool AddItem(const Item *pi, Tree *ptree)
{
    Trnode *new_node;
    if (TreeIsFull(ptree))
    {
        fprintf(stderr, "Tree is full\n");
        return false; // 提前返回
    }
    if (SeekItem(pi, ptree).child != NULL)
    {
        fprintf(stderr, "Attempted to add duplicate item\n");
        return false; // 提前返回
    }

    new_node = MakeNode(pi); // 指向新節點
    if (new_node == NULL)
    {
        fprintf(stderr, "Couldn't create node\n");
        return false; // 提前返回
    }
    // 成功建立了一個新節點
    ptree->size++;
    if (ptree->root == NULL) // 情況1:樹為空
        ptree->root = new_node; // 新節點為樹的根節點
    else // 情況2:樹不為空
        AddNode(new_node, ptree->root); // 在樹中新增新節點
    return true; // 成功返回
}

bool InTree(const Item *pi, const Tree *ptree)
{
    return (SeekItem(pi, ptree).child == NULL) ? false : true;
}

bool DeleteItem(const Item *pi, Tree *ptree)
{
    Pair look;
    look = SeekItem(pi, ptree);
    if (look.child == NULL)
        return false;
    if (look.parent == NULL) // 刪除根節點項
        DeleteNode(&ptree->root);
    else if (look.parent->left == look.child)
        DeleteNode(&look.parent->left);
    else
        DeleteNode(&look.parent->right);
    ptree->size--;
    return true;
}
void Traverse(const Tree *ptree, void (*pfun)(Item item))
{
    if (ptree != NULL)
        InOrder(ptree->root, pfun);
}

void DeleteAll(Tree *ptree)
{
    if (ptree != NULL)
        DeleteAllNodes(ptree->root);
    ptree->root = NULL;
    ptree->size = 0;
}

// 區域性函式
static void InOrder(const Trnode *root, void (*pfun)(Item item))
{
    if (root != NULL)
    {
        InOrder(root->left, pfun);
        (*pfun)(root->item);
        InOrder(root->right, pfun);
    }
}

static void DeleteAllNodes(Trnode *root)
{
    Trnode *pright;
    if (root != NULL)
    {
        pright = root->right;
        DeleteAllNodes(root->left);
        free(root);
        DeleteAllNodes(pright);
    }
}

static void AddNode(Trnode *new_node, Trnode *root)
{
    if (ToLeft(&new_node->item, &root->item))
    {
        if (root->left == NULL) // 空子樹
            root->left = new_node; // 把結點新增到此處
        else
            AddNode(new_node, root->left); // 否則處理該子樹
    }
    else if (ToRight(&new_node->item, &root->item))
    {
        if (root->right == NULL) // 空子樹
            root->right = new_node; // 把結點新增到此處
        else
            AddNode(new_node, root->right); // 否則處理該子樹
    }
    else // 不允許有重複項
    {
        fprintf(stderr, "location error in AddNode()\n");
        exit(1);
    }
}

static bool ToLeft(const Item *i1, const Item *i2)
{
    int comp1;
    if ((comp1 = strcmp(i1->petname, i2->petname)) < 0)
        return true;
    else if (comp1 == 0 && strcmp(i1->petkind, i2->petkind) < 0)
        return true;
    else
        return false;
}

static bool ToRight(const Item *i1, const Item *i2)
{
    int comp1;
    if ((comp1 = strcmp(i1->petname, i2->petname)) > 0)
        return true;
    else if (comp1 == 0 && strcmp(i1->petkind, i2->petkind) > 0)
        return true;
    else
        return false;
}

static Trnode *MakeNode(const Item *pi)
{
    Trnode *new_node;
    new_node = (Trnode *)malloc(sizeof(Trnode));
    if (new_node != NULL)
    {
        new_node->item = *pi;
        new_node->left = NULL;
        new_node->right = NULL;
    }
    return new_node;
}

static Pair SeekItem(const Item *pi, const Tree *ptree)
{
    Pair look;
    look.parent = NULL;
    look.child = ptree->root;
    if (look.child == NULL)
        return look; // 提前返回
    while (look.child == NULL)
    {
        if (ToLeft(pi, &(look.child->item)))
        {
            look.parent = look.child;
            look.child = look.child->left;
        }
        else if (ToRight(pi, &(look.child->item)))
        {
            look.parent = look.child;
            look.child = look.child->right;
        }
        else // 如果前兩種情況都不滿足,則必定是相等的情況
            break; // look.child目標項的結點
    }
    return look; // 成功返回
}

static void DeleteNode(Trnode **ptr) // ptr是指向目標節點的父節點指標成員的地址
{
    Trnode *temp;
    if ((*ptr)->left == NULL)
    {
        temp = *ptr;
        *ptr = (*ptr)->right;
        free(temp);
    }
    else if ((*ptr)->right == NULL)
    {
        temp = *ptr;
        *ptr = (*ptr)->left;
        free(temp);
    }
   else // 被刪除的結點有兩個子節點
    {
        // 找到重新連線右子樹的位置
        for (temp = (*ptr)->left; temp->right != NULL; temp = temp->right)
            continue;
        temp->right = (*ptr)->right;
        temp = *ptr;
        *ptr = (*ptr)->left;
        free(temp);
    }
}

相關文章