數據結構-二叉樹結尾+排序

一、二叉樹結尾

1、如何判斷一棵樹是完全二叉樹。

我們可以使用層序遍歷的思路,利用一個隊列,去完成層序遍歷,但是這里會有些許的不同,我們需要讓空也進隊列。如果隊列里到最后只剩下空那么這棵樹就是完全二叉樹。具體的實現如下:

借助了,按層序走,非空節點一定是連續的。

int TreeComplete(BTNode* root)
{assert(root);Queue q;QueueInit(&q);QueuePush(&q,root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);if (front == NULL)break;QueuePush(&q, front->left);QueuePush(&q, front->right);}//判斷是不是完全二叉樹//后面非空,說明非空節點不是連續的,不是完全二叉樹while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);if (front)return false;}QueueDestory(&q);return true;
}

二叉樹的銷毀

使用后序去銷毀

void TreeDestory(BTNode* root)
{if (root == NULL)return;TreeDestory(root->left);TreeDestory(root->right);free(root);
}

二、排序

1、插入排序。

把一個數據插入到有序的區間,定義一個end變量用來標識區間

void InsertSort(int* a, int n)
{for (int i = 1; i < n; i++){//單躺排序int temp = a[i];int end = i - 1; while (end >= 0){if (temp < a[end]){a[end + 1] = a[end];end--;}else{//為什么要break,這里會有end為-1的位置break;}}a[end + 1] = temp;}
}

?2、希爾排序

(1)預排序--目標:數組接近有序,分組插入排序,間隔為gap分為一組,對每組數據插入排序,假設gap == 3;

gap為3時的一趟直接插入排序

	int end;int gap = 3;int temp = a[end+gap];while (end >= 0){if (temp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}	}a[end + gap] = temp;

gap為3時的紅色組數據的排序

void ShellSort(int* a, int n)
{int gap = 3;for (int i = gap; i < n; i += gap){int end = i - gap;int temp = a[end + gap];while (end >= 0){if (temp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = temp;}}

三組都排完

void ShellSort(int* a, int n)
{int gap = 3;for (int j = 0; j < gap; j++){for (int i = gap + j; i < n; i += gap){int end = i - gap;int temp = a[end + gap];while (end >= 0){if (temp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = temp;}}
}

把上述代碼改成i++可以減少一層循環,就變成了多組并排的方式

gap到底時多少合適呢?

gap越大,跳的越快,越不接近有序

gap越小,跳的越慢,越接近有序?

gap = gap/1

gap = gap/3 + 1

//希爾排序
void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){gap = gap / 2;for (int j = 0; j < gap; j++){for (int i = gap + j; i < n; i += gap){int end = i - gap;int temp = a[end + gap];while (end >= 0){if (temp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = temp;}}}
}

(2)直接插入排序

gap為1的時候為直接插入排序

gap >1的時候為預排序

時間復雜度O(n^1.3)左右的樣子

3、選擇排序

我們可以找到最大的交換到右邊和最小的交換到左邊,但是如果left == maxi,一交換mini和left的值就會把maxi里的值交換到mini上。我們需要做一個修正,maxi = mini

void Swap(int* a, int* b)
{int temp = *a;*a = *b;*b = temp;
}
void SelectSort(int* a, int n)
{//首先進行選數int left = 0;int right = n - 1;while (left < right){int maxi = left, mini = left;for (int i = left; i <= right; i++){if (a[i] < a[mini])mini = i;if (a[i] > a[maxi])maxi = i;}//選數完畢交換兩個數Swap(&a[mini], &a[left]);//進行矯正if (left == maxi)maxi = mini;Swap(&a[maxi], &a[right]);left++;right--;}
}

選擇排序最壞和最好的時間復雜度為O(n^2)

4、堆排序已經在二叉樹堆已經講過了

5、冒泡排序

void BubbleSort(int* a, int n)
{for (int i = 0; i < n; i++){for (int j = 0; j < n - i; j++){if(a[j] > a[j+1])Swap(&a[j], &a[j + 1]);}}
}

優化:

void BubbleSort(int* a, int n)
{for (int i = 0; i < n; i++){bool exchange = false;for (int j = 0; j < n - i; j++){if (a[j] > a[j + 1]){Swap(&a[j], &a[j + 1]);//沒有改成true證明已經有序了exchange = true;}}if (exchange == false)break;}
}

部分有序時插入排序和冒泡排序就是有差距的。

6、快速排序

選出一個關鍵值key,把它放到正確的位置(最終排好序要在的位置)單趟排序

左邊放比key小的,右邊放比key大的

HOARE版本

左邊做key,右邊先走(相遇后,相遇位置正好是小的),右邊找比key小的。左邊找比key大的,然后交換

快排是一個遞歸的思想,分成左右區間,然后再排

? ? ? ? ?begin? = 0? ? keyi = end? ?

? ? ? ? end = 1? ? ? ? keyi + 1 = 2?? end = 1? ? 左大于等于右不存在這個區間? (遞歸的出口)

void QuickSort(int* a, int left,int right)
{if (left >= right){return;}int keyi = left;int begin = left, end = right;while (left < right){//相等的話沒有必要交換//不加前面的條件會越界訪問//一定先讓右先走//右邊找小while (left < right && a[right] >= a[keyi])right--;//左邊找大while(left < right && a[left] <= a[keyi])left++;Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);keyi = left;//[begin,keyi-1][keyi][keyi+1,end]QuickSort(a,begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

時間復雜度O(NlogN)

上面有一個錯誤應該是n-2^i+1

最壞的情況是逆序和順序的時候。它的時間復雜度就已經變到了O(N^2),可能會棧溢出。

優化

我們可以隨機選key也可以使用三數取中法選key

int GetMidNumi(int* a, int left, int right)
{int midi = (right + left) / 2;if (a[left] < a[midi]){if (a[midi] < a[right]){return midi;}else if (a[left] > a[right]){return left;}else{return right;}}else  //a[left] > a[midi]{if (a[midi] > a[right]){return midi;}else if (a[left] < a[right]){return left;}else{return right;}}
}
void QuickSort(int* a, int left,int right)
{if (left >= right){return;}/*int randi = left + (rand() % (right - left));Swap(&a[left], &a[randi]);*///三數取中找到下標//int keyi = left;int midi = GetMidNumi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int begin = left, end = right;while (left < right){//相等的話沒有必要交換//不加前面的條件會越界訪問//一定先讓右先走//右邊找小while (left < right && a[right] >= a[keyi])right--;//左邊找大while(left < right && a[left] <= a[keyi])left++;Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);keyi = left;//[begin,keyi-1][keyi][keyi+1,end]QuickSort(a,begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

那么為什么相遇的位置一定比key小

左邊做key,右邊先走,保證相遇位置比key要小

1、R找小,L找大沒有找到,L遇到R或者就是key的位置

2、R找小找不到,R直接跟L相遇,要么就是一個比key小的位置,或者直接到keyi

類似的道理右邊做key左邊先走也是這樣的。相遇的位置比key要大

挖坑法


?

void QuickSort2(int* a, int left, int right)
{if (left >= right){return;}/*int randi = left + (rand() % (right - left));Swap(&a[left], &a[randi]);*///三數取中找到下標//int keyi = left;int midi = GetMidNumi(a, left, right);Swap(&a[left], &a[midi]);int begin = left, end = right;int key = a[left];int hole = left;while (left < right){//相等的話沒有必要交換//不加前面的條件會越界訪問//一定先讓右先走//右邊找小while (left < right && a[right] >= key)right--;//找到以后把值放到left上面a[hole] = a[right]; //形成新的坑位hole = right;//左邊找大while (left < right && a[left] <= key)left++;//找到后把值放到right上面a[hole] = a[left]; //形成新的坑位hole = left;}a[hole] = key;//[begin,keyi-1][keyi][keyi+1,end]QuickSort(a, begin, hole - 1);QuickSort(a, hole + 1, end);
}

雙指針法

1、cur找到比key小的值++prev,cur和prev位置的值交換,++cur

2、cur找到比key大的值,++cur

說明:prev要么緊跟著cur(prev下一個就是cur)

prev跟cur中間間隔著比key大的一段值的區間。

把比key大的值往右翻,比key小的值,翻到左邊。

void QuickSort3(int* a, int left, int right)
{if (left >= right)return;int midi = GetMidNumi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int cur = left + 1;int prev = left;while (cur <= right){//cur找到比key小的值,++prev,然后交交換兩個位置的值,cur++//cur找到比key大的值,++curif (a[keyi] > a[cur]&&++prev != cur)Swap(&a[prev], &a[cur]);cur++;}Swap(&a[prev], &a[keyi]);keyi = prev;QuickSort3(a, left, keyi-1);QuickSort3(a, keyi + 1, right);
}

小區間優化

到了遞歸的最后三層的時候,我們可以使用直接插入排序來排序,這樣我們會減少百分之87.5的遞歸。這樣的優化為小區間優化。小區間直接插入排序

void QuickSort3(int* a, int left, int right)
{if (left >= right)return;//加上小區間優化if ((right - left + 1) > 10){int keyi = PartSort3(a, left, right);QuickSort3(a, left, keyi - 1);QuickSort3(a, keyi + 1, right);}else{InsertSort(a + left, right - left + 1);}
}

快排的非遞歸

遞歸的問題

效率,深度太深,會棧溢出。

遞歸改非遞歸

直接改成循環

使用棧輔助改循環?

如何改非遞歸,遞歸棧幀里面放的是區間。區間在變化,所以我們可以在棧里面存區間。最開始存0 - 9,進行單趟排,左區間是0 4,有區間是[ 6 ,9] 可以把這兩個區間入棧,每次入棧如此反復。先入右區間,再入左區間。

1、棧里面取一段區間,單趟排序

2、單趟分割子區間入棧

3、子區間只有一個值或者不存在就不入棧

//實質就是利用自己實現的棧,來模擬編譯器中的棧幀
void QuickSortNonR(int* a, int left, int right)
{ST st;STInit(&st);//先讓0-9區間入棧STPush(&st, right);STPush(&st, left);while (!STEmpty(&st)){//第二步,出棧,得到區間int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);//取出區間后進行一趟排序int keyi = PartSort3(a, begin, end);//然后劃分出區間//[begin,keyi-1][keyi][keyi+1,end]if (keyi + 1 < end){STPush(&st, end);STPush(&st, keyi + 1);}if (begin < keyi - 1){STPush(&st, keyi - 1);STPush(&st, begin);}}STDestory(&st);
}

7、歸并排序

兩個有序區間歸并:依次比較,小的尾插到新空間。

但是不滿足有序區間呢。我們可以使用分治的思想,使左右區間有序。相當于二叉樹的后續遍歷,先分區間,再歸并。時間復雜度為O(NlogN)。

開一個臨時數組,歸并到臨時數組,完了以后再拷貝回去。遞歸左區間再遞歸右區間

左右有序,再歸并

void _MergeSort(int* a, int left, int right, int* temp)
{//遞歸返回條件if (left >= right){return;}//首先劃分區間int mid = (right + left) / 2;//然后使左右區間有序,采用分治的思想//[left,mid][mid + 1,right]_MergeSort(a, left, mid, temp);_MergeSort(a, mid + 1, right, temp);//接下來進行歸并int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;//誰小就尾插進去int i = left;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){temp[i++] = a[begin1++];}else{temp[i++] = a[begin2++];}}while (begin1 <= end1){temp[i++] = a[begin1++];}while (begin2 <= end2){temp[i++] = a[begin2++];}//歸并完成進行拷貝memcpy(a + left, temp + left, sizeof(int) * (right - left + 1));
}
void MergeSort(int* a, int n)
{int* temp = (int*)malloc(sizeof(int) * n);if (temp == NULL){perror("malloc");return;}_MergeSort(a, 0, n - 1, temp);free(temp);
}

歸并排序的非遞歸可以使用循環來實現,思路是

但是這個方法的邊界處理有點麻煩
gap是歸并過程中每組的個數,邊界的控制

第一組:[i, i+gap-1]

第二組:[i + gap,i+2*gap-1]

那么如果遇到是奇數個可能會導致越界訪問。

1、end1越界了怎么辦? 不歸并了

?2、end1沒有越界 begin2越界了,跟1一樣處理

?3、end2越界了,前面的都沒有越界,修正end2到n-1

void MergeSortNonR(int* a, int n)
{int* temp = (int*)malloc(sizeof(int) * n);if (temp == NULL){perror("malloc");return;}int gap = 1;//接下來進行歸并while (gap < n){for (int i = 0; i < n; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//誰小就尾插進去//如果是奇數個怎么辦,我們可以分類討論進行修正if (end1 >= n || begin2 >= n){break;}else if (end2 >= n){end2 = n - 1;}int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){temp[j++] = a[begin1++];}else{temp[j++] = a[begin2++];}}while (begin1 <= end1){temp[j++] = a[begin1++];}while (begin2 <= end2){temp[j++] = a[begin2++];}//歸并完成進行拷貝memcpy(a + i, temp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}free(temp);
}

?上面的排序除了歸并排序外,都是內排序,也就是在內存中排序。歸并排序內外都可以。

非比較排序:

1、計數排序

統計每個數據出現的個數

進行排序

總結:

void CountSort(int* a,int n)
{//先求出范圍int max = a[0], min = a[0];for (int i = 0; i < n; i++){if (a[i] > max)max = a[i];if (a[i] < min)min = a[i];}int range = max - min + 1;//求出每個數出現的次數int* countA = (int*)malloc(sizeof(int) * range);memset(countA, 0, sizeof(int) * range);for (int i = 0; i < n; i++){countA[a[i] - min]++;}//進行排序int j = 0;for (int i = 0; i < range; i++){while (countA[i]--){a[j++] = i + min;}}free(countA);
}

2、基數排序

3、桶排序

上面的排序基本上不會用到,這里就不在描述

時間復雜度總結

穩定性:相同數據的相對順序是否穩定,注意是相等的數談穩定性

穩定:冒泡排序,插入排序,歸并排序。

不穩定:選擇排序,希爾排序,堆排序,快速排序。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/9898.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/9898.shtml
英文地址,請注明出處:http://en.pswp.cn/web/9898.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

js 數據格式轉換,對象轉數組,數組轉對象

1.對象轉數組 // 對象obj轉換成數組格式 let obj { orgCode:分局編碼, alertId:告警ID, name:告警名稱 } let arr [] for(let key in obj) { console.log(11,key,obj[key]); // 定義一個對象&#xff0c;賦值 let o { id: key, // key是obj對象的鍵值 label: obj[key] …

學習Vue 3.0中的onMounted和onUnmounted鉤子函數

學習Vue 3.0中的onMounted和onUnmounted鉤子函數 一、什么是onMounted和onUnmounted&#xff1f;二、如何使用onMounted和onUnmounted&#xff1f;1、使用onMounted2、使用onUnmounted 三、總結 一、什么是onMounted和onUnmounted&#xff1f; Vue 3.0帶來了許多令人興奮的新特…

Modal h函數寫法

Modal h函數寫法 if (res.data.flag) {const ocapWarn res.data.ocaplList;Modal.warning({title: "提示",content: h("div", {}, [ocapWarn.map((item, index) > {return h("div", {}, [h("p",${index 1}、${item.defectItem}(…

IntersectionObserver對象

IntersectionObserver對象 IntersectionObserver對象&#xff0c;從屬于Intersection Observer API&#xff0c;提供了一種異步觀察目標元素與其祖先元素或頂級文檔視窗viewport交叉狀態的方法&#xff0c;祖先元素與視窗viewport被稱為根root&#xff0c;也就是說Intersectio…

c#---多態

在 C#語言中體現多態有三種方式&#xff1a;虛方法&#xff0c;抽象類&#xff0c; 接口 一、虛方法 什么是虛方法&#xff1f; 在父類中使用 virtual 關鍵字修飾的方法&#xff0c; 就是虛方法。在子類中可以使用 override 關鍵字對該虛方法進行重寫。 class Animal {public…

android apk沒有源碼如何修改程序

如果您擁有一個APK文件但沒有源代碼&#xff0c;您可以嘗試以下幾種方法來進行修改&#xff1a; 反編譯APK&#xff1a;使用工具如apktool對APK文件進行反編譯&#xff0c;這將為您提供源代碼和資源文件。 動態調試&#xff1a;使用調試工具連接設備或模擬器&#xff0c;并動態…

重裝前端整體流程

用戶管理 --匯總 -- 明細-CSDN博客 一、node 這個看環境變量 2023最新版Node.js下載安裝及環境配置教程&#xff08;非常詳細&#xff09;從零基礎入門到精通&#xff0c;看完這一篇就夠了_nodejs安裝及環境配置-CSDN博客 配置到國內鏡像的時候&#xff0c;去看&#xff0c;淘…

理解固化的Maven依賴:spring-boot-starter-parent 與 spring-boot-dependencies

目錄 理解固化的Maven依賴&#xff1a;spring-boot-starter-parent 與 spring-boot-dependencies1. spring-boot-starter-parent1.1 簡介1.2 特點 2. spring-boot-dependencies2.1 簡介2.2 特點 3. 異同點對比3.1 相同點3.2 不同點案例一&#xff1a;使用 spring-boot-starter-…

Java方法的重載

方法重載 1. 為什么需要方法重載 public class TestMethod{public static void main (String[] args){int a 10;int b 20;int ret add(a,b);System.out.println("ret "ret);double a2 10.5;double b2 20.5;double ret2 add(a2,b2);System.out.println("…

《QT實用小工具·六十二》基于QT實現貝塞爾曲線畫炫酷的波浪動畫

1、概述 源碼放在文章末尾 該項目實現了通過貝塞爾曲線畫波浪動畫&#xff0c;可控制 顏色密度速度加速度 安裝與運行環境 語言&#xff1a;C 框架&#xff1a;Qt 11.3 平臺&#xff1a;Windows 將屏幕水平平均分為10塊&#xff0c;在一定范圍內隨機高度的12個點&#xff08;…

飛天使-k8s知識點29-kubernetes安裝1.28.0版本

文章目錄 選用版本初始化服務器,自己修改里面的ipreboot haproxy安裝 &#xff0c;可以參考我之前寫的內核參數調整&#xff0c;安裝docker 安裝cri-dockerd開始安裝集群工具下載鏡像以及啟用完畢之后 此時的coredns 不通結果展示 選用版本 k8s 1.24版本之前還可以使用docker&…

【初階數據結構】順序表OJ題講解

前言 &#x1f4da;作者簡介&#xff1a;愛編程的小馬&#xff0c;正在學習C/C&#xff0c;Linux及MySQL。 &#x1f4da;本文收錄與初階數據結構系列&#xff0c;本專欄主要是針對時間、空間復雜度&#xff0c;順序表和鏈表、棧和隊列、二叉樹以及各類排序算法&#xff0c;持…

基于ambari hdp的kafka用戶授權讀寫權限

基于ambari hdp的kafka用戶授權讀寫權限 版本Kafka 2.0.0添加自定義配置修改admin密碼重啟kafka授權讀取授權寫入有效通配符部分舉例 版本Kafka 2.0.0 添加自定義配置 authorizer.class.name kafka.security.auth.SimpleAclAuthorizer super.users User:admin allow.everyo…

【LLM 論文】Step-Back Prompting:先解決更高層次的問題來提高 LLM 推理能力

論文&#xff1a;Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models ???? Google DeepMind, ICLR 2024, arXiv:2310.06117 論文速讀 該論文受到的啟發是&#xff1a;人類再解決一個包含很多細節的具體問題時&#xff0c;先站在更高的層次上解…

Android 屏幕適配全攻略(上)-掌握屏幕單位,應對千變萬化的設備

本文從 Android 開發中常見的長度單位 px、dp、sp 入手&#xff0c;詳細介紹了它們的特點及轉換關系。 接著深入探討了屏幕尺寸、分辨率、像素密度等重要的屏幕指標&#xff0c;幫助讀者全面理解它們之間的聯系。最后&#xff0c;通過實例代碼演示了如何在代碼中進行單位轉換&…

三分鐘上手安全滲透系統Kali Linux

kali linux系統集成了常用的安全滲透工具&#xff0c;省去了安裝工具的時間&#xff0c;做安全相關的工作是非常推薦使用的。 安裝Kalii Linux 安裝系統 一般使用虛擬機進行安裝&#xff0c;Kali Linux基于Debian內核&#xff0c;虛擬機的操作系統選擇Debian 7.x 64 選擇系統…

【SRC實戰】一鍵完成全部任務獲取獎勵

挖個洞先 https://mp.weixin.qq.com/s/LkPfJuuP1K8vaFXRn-8wVg “ 以下漏洞均為實驗靶場&#xff0c;如有雷同&#xff0c;純屬巧合 ” 01 — 漏洞證明 一、業務邏輯 “ 如何欺騙APP完成任務獲取獎勵&#xff1f; ” 1、記錄金幣數量20 2、瀏覽商品詳情頁 3、點擊瀏覽提…

我們應該如何做參與式觀察

記得多年以前&#xff0c;有個朋友問我&#xff1a;對于做觀察&#xff0c;有人通過教授繪畫技巧來教人如何做觀察。你們研究員又不會畫畫&#xff0c;你們如何讓人相信你們更會觀察呢&#xff1f;坦率說&#xff0c;當時我被問住了&#xff0c;因為我從來沒有進行過這樣的對比…

day5Qt作業

服務器端 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//準備組件&#xff0c;初始化組件狀態this->setFixedSize(800,600);chatwidget new QListWidge…

代碼隨想錄算法訓練營第四十九天| 123.買賣股票的最佳時機III,188.買賣股票的最佳時機IV

目錄 題目鏈接&#xff1a;123.買賣股票的最佳時機III 思路 代碼 題目鏈接&#xff1a;188.買賣股票的最佳時機IV 思路 代碼 總結 題目鏈接&#xff1a;123.買賣股票的最佳時機III 思路 與之前買賣股票不同的是本題要求最多買賣兩次&#xff0c;那么dp數組以及遞推公式都…