iOS 面試之數據結構和算法分析(快排,希爾排序,堆排序,冒泡排序,選擇排序)

常見的數據結構類型

1.集合結構 線性結構 樹形結構 圖形結構
1.1、集合結構 說白了就是一個數學意義上的集合,就是一個圓圈中有很多個元素,元素與元素之間沒有任何關係 , 這個很簡單
1.2、線性結構 說白了就是一個條線上站着很多個人。 這條線不一定是直的。也可以是彎的。也可以是值的 相當於一條線被分成了好幾段的樣子 (發揮你的想象力)。 線性結構是一對一的關係
1.3、樹形結構 說白了就是一棵倒立的樹,子節點是就像是大樹的樹枝和樹葉。 做開發的肯定或多或少的知道xml 解析 樹形結構跟他非常類似。也可以想象成一個金字塔。樹形結構是一對多的關係
1.4、圖形結構 這個就比較複雜了。他呢 無窮。無邊 無向(沒有方向箭頭)圖形機構 你可以理解爲多對多 類似於我們人的交集關係

2. 數據結構的存儲

2.1 順序存儲結構發揮想象力啊。 舉個列子。
數組。1-2-3-4-5-6-7-8-9-10。這個就是一個順序存儲結構 ,存儲是按順序的 舉例說明啊。
棧。做開發的都熟悉。棧是先進後出 ,後進先出的形式 對不對 ?!他的你可以這樣理解hello world 在棧裏面從棧底到棧頂的邏輯依次爲 h-e-l-l-o-w-o-r-l-d 這就是順序存儲 再比如 隊列 ,隊列是先進先出的對吧,從頭到尾 h-e-l-l-o-w-o-r-l-d 就是這樣排對的
2.2 鏈式存儲結構再次發揮想象力 這個稍微複雜一點 這個圖片我一直弄好 ,回頭找美工問問,再貼上 例如 還是一個數組1-2-3-4-5-6-7-8-9-10 鏈式存儲就不一樣了 1(地址)-2(地址)-7(地址)-4(地址)-5(地址)-9(地址)-8(地址)-3(地址)-6(地址)-10(地址)。每個數字後面跟着一個地址 而且存儲形式不再是順序 ,也就說順序亂了,1(地址) 1後面跟着的這個地址指向的是2,2後面的地址指向的是3,3後面的地址指向是誰你應該清楚了吧。他執行的時候是 1(地址)-2(地址)-3(地址)-4(地址)-5(地址)-6(地址)-7(地址)-8(地址)-9(地址)-10(地址),但是存儲的時候就是完全隨機的。明白了?!

**3 單向鏈表\雙向鏈表\循環鏈表還是舉例子。**理解最重要。不要去死記硬背 哪些什麼。定義啊。邏輯啊。理解纔是最重要滴
3.1 單向鏈表 A->B->C->D->E->F->G->H. 這就是單向鏈表 H 是頭 A 是尾 像一個只有一個頭的火車一樣 只能一個頭拉着跑
3.2 雙向鏈表 H<- A->B->C->D->E->F->G->H. 這就是雙向鏈表。有頭沒尾。兩邊都可以跑 跟地鐵一樣 到頭了 可以倒着開回來
3.3 循環鏈表 發揮想象力 A->B->C->D->E->F->G->H. 繞成一個圈。就像蛇喫自己的這就是循環 不需要去死記硬背哪些理論知識。
4.二叉樹/平衡二叉樹
4.1 什麼是二叉樹
樹形結構下,兩個節點以內 都稱之爲二叉樹 不存在大於2 的節點 分爲左子樹 右子樹 有順序 不能顛倒 ,懵逼了吧,你肯定會想這是什麼玩意,什麼左子樹右子樹 ,都什麼跟什麼鬼? 現在我以普通話再講一遍,你把二叉樹看成一個人 ,人的頭呢就是樹的根 ,左子樹就是左手,右子樹就是右手,左右手可以都沒有(殘疾嘛,聲明一下,絕非歧視殘疾朋友,勿怪,勿怪就是舉個例子,i am very sorry) , 左右手呢可以有一個,就是不能顛倒。這樣講應該明白了吧 二叉樹有五種表現形式
1. 空的樹(沒有節點)可以理解爲什麼都沒 像空氣一樣
2. 只有根節點。 (理解一個人只有一個頭 其他的什麼都沒,說的有點恐怖)
3. 只有左子樹 (一個頭 一個左手 感覺越來越寫不下去了)
4. 只有右子樹
5. 左右子樹都有二叉樹可以轉換成森林 樹也可以轉換成二叉樹。
這裏就不介紹了 你做項目絕對用不到數據結構大致介紹這麼多吧。理解爲主 別死記,死記沒什麼用

5.算法

5.1 冒泡排序 和選擇排序
冒泡排序:冒泡排序的定義就不提了,總結起來就一句話(劃重點):從左到右,數組中相鄰的兩個元素進行比較,將較大的放到後面

我們同樣,以上面的例子爲例 int [] a= {2,6,5,3,1};

在這裏插入圖片描述

從圖可以看出,第一輪比較,比較了4輪,找出了最小數1,與第一個位置的數字進行了換位;

第二輪排序開始時的數組已經變成了{1,6,5,3,2};
在這裏插入圖片描述

從圖可以看出,第二輪比較,比較了3次,確定剩餘數中的最小數爲2,與第二個位置的數交換。

第三輪排序開始時的數組已經變成了{1,2,5,3,6};
在這裏插入圖片描述

從圖可以看出,第三輪比較,比較了2次,確定了剩餘數中最小的數3,與第三個位置的數互換位置。

第四輪排序開始時的數組已經變成了{1,2,3,5,6};

在這裏插入圖片描述
從圖可以看出,第四輪比較,比較了1次,確定了剩餘數中最小的數5,放在了第4個位置。

這樣4輪比較後,這組數已經排序好了,接下來同上,去找規律,實現代碼了:

在這裏插入圖片描述
運行結果:
在這裏插入圖片描述
選擇排序,總結一句話就是(劃重點):從第一個位置開始比較,找出最小的,和第一個位置互換,開始下一輪。
我們同樣,以上面的例子爲例 int [] a= {2,6,5,3,1};
在這裏插入圖片描述

從圖可以看出,第一輪比較,比較了4輪,找出了最小數1,與第一個位置的數字進行了換位;

第二輪排序開始時的數組已經變成了{1,6,5,3,2};

在這裏插入圖片描述

從圖可以看出,第二輪比較,比較了3次,確定剩餘數中的最小數爲2,與第二個位置的數交換。

第三輪排序開始時的數組已經變成了{1,2,5,3,6};

在這裏插入圖片描述
從圖可以看出,第三輪比較,比較了2次,確定了剩餘數中最小的數3,與第三個位置的數互換位置。

第四輪排序開始時的數組已經變成了{1,2,3,5,6};

                   ![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20190721221106174.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTQxNTYyMw==,size_16,color_FFFFFF,t_70)

從圖可以看出,第四輪比較,比較了1次,確定了剩餘數中最小的數5,放在了第4個位置。

這樣4輪比較後,這組數已經排序好了,接下來同上,去找規律,實現代碼了:

在這裏插入圖片描述
運行結果:
在這裏插入圖片描述

選擇排序也就結束了,這樣一弄有沒有更清楚呢?

那麼好,是時候來總結下他們的區別了(劃重點)。

(1)冒泡排序是比較相鄰位置的兩個數,而選擇排序是按順序比較,找最大值或者最小值;

(2)冒泡排序每一輪比較後,位置不對都需要換位置,選擇排序每一輪比較都只需要換一次位置;

(3)冒泡排序是通過數去找位置,選擇排序是給定位置去找數;

冒泡排序優缺點:優點:比較簡單,空間複雜度較低,是穩定的;
缺點:時間複雜度太高,效率慢;

選擇排序優缺點:優點:一輪比較只需要換一次位置; 缺點:效率慢,不穩定(舉個例子5,8,5,2,9 我們知道第一遍選擇第一個元素5會和2交換,那麼原序列中2個5的相對位置前後順序就破壞了)
5.2 插入排序
**算法思路:**插入排序的工作方式非常像人們排序一手撲克牌一樣。開始時,我們的左手爲空並且桌子上的牌面朝下。然後,我們每次從桌子上拿走一張牌並將它插入左手中正確的位置。爲了找到一張牌的正確位置,我們從右到左將它與已在手中的每張牌進行比較,
算法步驟:

  1. 從第一個元素開始,該元素可以認爲已經被排序
  2. 取出下一個元素,在已經排序的元素序列中從後向前掃描
  3. 如果該元素(已排序)大於新元素,將該元素移到下一位置
  4. 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置
  5. 將新元素插入到該位置後
    在這裏插入圖片描述
    代碼實現:
class InsertionSort 
{ 
    /*Function to sort array using insertion sort*/
    void sort(int arr[]) 
    { 
        int n = arr.length; 
        for (int i=1; i<n; ++i) 
        { 
            int key = arr[i]; 
            int j = i-1; 
  
            /* Move elements of arr[0..i-1], that are 
               greater than key, to one position ahead 
               of their current position */
            while (j>=0 && arr[j] > key) 
            { 
                arr[j+1] = arr[j]; 
                j = j-1; 
            } 
            arr[j+1] = key; 
        } 
    } 
  
    /* A utility function to print array of size n*/
    static void printArray(int arr[]) 
    { 
        int n = arr.length; 
        for (int i=0; i<n; ++i) 
            System.out.print(arr[i] + " "); 
  
        System.out.println(); 
    } 
  
    // Driver method 
    public static void main(String args[]) 
    {         
        int arr[] = {12, 11, 13, 5, 6}; 
  
        InsertionSort ob = new InsertionSort();         
        ob.sort(arr); 
          
        printArray(arr); 
    } 
}

5.3 希爾排序
(1)希爾排序(shell sort)這個排序方法又稱爲縮小增量排序,是1959年D·L·Shell提出來的。該方法的基本思想是:設待排序元素序列有n個元素,首先取一個整數increment(小於n)作爲間隔將全部元素分爲increment個子序列,所有距離爲increment的元素放在同一個子序列中,在每一個子序列中分別實行直接插入排序。然後縮小間隔increment,重複上述子序列劃分和排序工作。直到最後取increment=1,將所有元素放在同一個子序列中排序爲止。
(2)由於開始時,increment的取值較大,每個子序列中的元素較少,排序速度較快,到排序後期increment取值逐漸變小,子序列中元素個數逐漸增多,但由於前面工作的基礎,大多數元素已經基本有序,所以排序速度仍然很快。
(3)希爾排序舉例:
1>下面給出一個數據列:
在這裏插入圖片描述
2>第一趟取increment的方法是:n/3向下取整+1=3(關於increment的取法之後會有介紹)。將整個數據列劃分爲間隔爲3的3個子序列,然後對每一個子序列執行直接插入排序,相當於對整個序列執行了部分排序調整。圖解如下:

3>第二趟將間隔increment= increment/3向下取整+1=2,將整個元素序列劃分爲2個間隔爲2的子序列,分別進行排序。圖解如下:

4>第3趟把間隔縮小爲increment= increment/3向下取整+1=1,當增量爲1的時候,實際上就是把整個數列作爲一個子序列進行插入排序,圖解如下:

5>直到increment=1時,就是對整個數列做最後一次調整,因爲前面的序列調整已經使得整個序列部分有序,所以最後一次調整也變得十分輕鬆,這也是希爾排序性能優越的體現。
(4)希爾排序算法的代碼實現(C++)

//函數功能,希爾排序算法對數字遞增排序
//函數參數,數列起點,數列終點
void shell_sort(const int start, const int end) {
    int increment = end - start + 1;    //初始化劃分增量
    int temp{ 0 };
    do {    //每次減小增量,直到increment = 1
        increment = increment / 3 + 1;
        for (int i = start + increment; i <= end; ++i) {    //對每個劃分進行直接插入排序
            if (numbers[i - increment] > numbers[i]) {
                temp = numbers[i];
                int j = i - increment;
                do {    //移動元素並尋找位置
                    numbers[j + increment] = numbers[j];
                    j -= increment;
                } while (j >= start && numbers[j] > temp);
                numbers[j + increment] = temp;  //插入元素
            }
        }
    } while (increment > 1);
}

上面的函數的第一個do……while控制increment每次的縮小,其內部便是直接插入排序算法的使用,與直接插入排序算法稍有不同的一點是:其j每次的變化量是increment而不是1。
(5)關於希爾排序increment(增量)的取法。
增量increment的取法有各種方案。最初shell提出取increment=n/2向下取整,increment=increment/2向下取整,直到increment=1。但由於直到最後一步,在奇數位置的元素纔會與偶數位置的元素進行比較,這樣使用這個序列的效率會很低。後來Knuth提出取increment=n/3向下取整+1.還有人提出都取奇數爲好,也有人提出increment互質爲好。應用不同的序列會使希爾排序算法的性能有很大的差異。
(6)希爾排序應該注意的問題
從上面圖解希爾排序的過程可以看到,相等的排序碼25在排序前後的順序發生了顛倒,所以希爾排序是一種不穩定的排序算法。
5.4 二分查找

二分查找法實質上是不斷地將有序數據集進行對半分割,並檢查每個分區的中間元素。在以下介紹的實現方法中,有序數據集存放在sorted中,sorted是一塊連續的存儲空間。參數target是要查找的數據。

此實現過程的實施是通過變量left和right控制一個循環來查找元素(其中left和right是正在查找的數據集的兩個邊界值)。首先,將left和right分別設置爲0和size-1。在循環的每次迭代過程中,將middle設置爲left和right之間區域的中間值。如果處於middle的元素比目標值小,將左索引值移動到middle後的一個元素的位置上。即下一組要搜索的區域是當前數據集的上半區。如果處於middle的元素比目標元素大,將右索引值移動到middle前一個元素的位置上。即下一組要搜索的區域是當前數據集的下半區。隨着搜索的不斷進行,left從左向右移,right從右向左移。一旦在middle處找到目標,查找將停止;如果沒有找到目標,left和right將重合。圖示如下:
在這裏插入圖片描述
5.5 快排

快速排序原理:從一組數中任意選出一個數,將大於它的數放右邊,小於它的數放左邊,然後再從左邊和右邊的倆組數中分別執行此操作,知道組中元素數爲1,此時,數組就是有序的了。
實現代碼:

 int partsort(int a[],int l,int r){                 //將比a[r]小的元素放左邊,比它大的放右邊,最後把a[r]放中間
 2 
 3                 int i=l;                                        //i爲比a[r]大的元素的下標,初始爲開始位置l
 4 
 5                 for(int j=l;j<r;j++){                           
 6 
 7                  if(a[j]<=a[r]){                               //如果元素比a[r]小則和大的元素交換位置,目的讓小的放一起,大的放一起
 8 
 9                    int t=a[j];                                 //可以自己手運行幾遍這個循環
10 
11                        a[j]=a[i];
12 
13                        a[i]=t;
14 
15                        i++;
16 
17                   }
18 
19                }
20 
21               int t=a[i];                                   //a[i]爲小元素和大元素的交界處,將a[r]與之交換
22 
23                   a[i]=a[r];
24 
25                   a[r]=t;
26 
27               return i;                                    //返回交界處下標,繼續排前邊的和後邊的這倆組
28 
29       }
30 
31            void quicksort(int a[],int l,int r){
32 
33                if(l<r){                                   //遞歸結束條件爲組中只剩一個元素
34 
35                int p=partsort(a,l,r);                    //分成倆組返回交界處
36 
37                    quicksort(a,l,p-1);                  //繼續分左邊
38 
39                    quicksort(a,p+1,r);
40 
41                 }
42 
43            }

4、堆排序

要想理解堆排序,首先你要知道最大堆,要想理解最大堆,你得知道二叉樹。
二叉樹:每個節點最多有倆個孩子節點。
最大堆:父親節點的值總是大於孩子節點的值。
當然在這裏二叉樹的存儲結構不是鏈表,是使用數組存的:
(1)數組下標爲0處是根節點。
(2)父親節點下標2爲左孩子下標,父親節點下標2+1爲右孩子下標。
根據這倆條準則我們就可以將二叉樹存在數組了。
堆排序原理:我們知道最大堆的性質(父親節點的值總是大於孩子節點的值),那麼根節點處不就是當前數列的最大值嗎,那麼我們每次取根節點的值放在末尾,然後將最大堆的大小-1,更新最大堆,取根節點放後邊…不斷執行這個過程,直到最大堆中只剩一個元素,此時數組就是一個有序數組了。

根據原理可以看出我們需要編的操作有
(1)建最大堆
(2)更新最大堆,其實建立最大堆就是不斷更新最大堆的過程,如果我們將每個結點都執行一遍更新最大堆操作(即父親節點的值總是大於孩子節點的值,不符合的話將父親節點與最大的孩子交換位置),當然執行順序必須是從下往上,然後只需從非葉子節點開始執行就好了(非葉子節點就是有孩子的結點)。
堆排序代碼如下:

//更新最大堆操作
2 
3           void dfDui(int x) {                         //參數爲父親節點下標
4 
5             int lchild=x*2;                           //左孩子下標
6 
7             int rchild=x*2+1;                         //右孩子下標
8 
9             int max=x;                                //最大值下標初始爲父親下標
10 
11           if(lchild<size&&a[lchild]>a[max])           //比較找出最大值
12 
13                max=lchild;
14 
15           if(rchild<size&&a[rchild]>a[max])
16 
17               max=rchild;
18 
19           if(max!=x){           //若父親節點爲最大值,則符合性質,否則交換,將最大值移到父親節點處,然後因爲孩子節點處已改變,更新此節點。
20 
21             int t=a[max];
22 
23                 a[max]=a[x];
24 
25                 a[x]=t;
26 
27             dfDui(max);
28 
29            }
30 
31        }
32 
33    //建最大堆操作
34 
35           void creatDui(){
36 
37           for(int i=a.length/2+1;i>=0;i--){     //葉子結點數爲結點總數一半且都在最後(可以從孩子節點下標的算法爲父親節點*2看出),因此                     duDui(i);                       // a.length/2+1處開始爲非葉子節點     
38 
39           }
40 
41       }
42 
43    //堆排序操作
44 
45        void sort(){
46 
47         creatDui();                                //建最大堆
48 
49         for(int i=size-1;i>=1;i--){               //每次將第一個數與最後一個數交換,然後大小-1,更新已經改變的根節點
50 
51             int t=a[0];
52 
53                 a[0]=a[size-1];
54 
55                 a[size-1]=t;
56 
57                 size--;
58 
59                 dfDui(0);
60 
61          }
62 
63      }

轉載鏈接:https://www.jianshu.com/p/1b43f4eac8d8
參考原文:https://blog.csdn.net/zxm317122667/article/details/83344178
參考原文:https://blog.csdn.net/weixin_37818081/article/details/79202115

給大家推薦一個優秀的iOS交流平臺,平臺裏的夥伴們都是非常優秀的iOS開發人員,我們專注於技術的分享與技巧的交流,大家可以在平臺上討論技術,交流學習。歡迎大家的加入(密碼:001在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章