算法 排序


前言:

排序算法是我們編程中遇到最多的算法。目前主流的算法有8種。

  平均時間復雜度從高到低依次是:

     冒泡排序(o(n2),穩定),選擇排序(o(n2),不穩定),插入排序(o(n2), 不穩定),堆排序(o(nlogn), 不穩定),

     歸並排序(o(nlogn), 穩定),快速排序(o(nlogn), 不穩定), 希爾排序(o(n1.25), 不穩定),基數排序(o(n), 穩定)

申明:以下排序全部針對數組{ 25, 1, 5, 2, 9, 11, 2, 4 };   所有測試使用備注,只是為了讓用戶根據結果看得更加明白,理解之后可刪除。

一、冒泡排序穩定

思想:從數組第一位開始,每個元素和它下一位比較,將較大的換到后面,即每一輪循環之后可以確定一個位置。提高效率(可定義一個標記,如果一輪循環之后沒有發生交換直接結束)

代碼:

   static void maopao()
        {
            int[] a = new int[] { 25, 1, 5, 2, 9, 11, 2, 4 }; 
            bool sign = false;
            for (int i = 0; i < a.Length - 1; i++)
            {
                for (int j = 0; j < a.Length - i - 1; j++)
                {
                    if (a[j] > a[j+1])
                    {
                        outPuts(a, "交換之前");//測試使用
                        Console.Write(String.Format("    {0:D2}<-交換->{1:D2}   ", a[j], a[j + 1]));//測試使用
                        int b = a[j];
                        a[j] = a[j + 1];
                        a[j + 1] = b;
                        sign = true;
                        outPuts(a, "交換之后");//測試使用
                        Console.WriteLine();//測試使用
                    }
                }
                if (!sign) break;
            }
            Console.WriteLine();//測試使用
            outPuts(a, "最后數據");
            Console.ReadKey();
        }

        private static void outPuts(int[] a, string str)
        {
            Console.Write(str + ":");
            foreach (var item in a)
            {
                Console.Write(item + " ");
            }
        }

 

結果:

 

二、選擇排序不穩定

對冒泡排序進行優化,由上結果可以看到25連續交換多次,那么如何實現一次循環只交換一次就可以確定一個位置呢?

思想:對於第一趟,搜索整個數組,尋找出最小(或最大此處以最小為例即從小到大排序)的,然后放置在數組的0號位置;對於第二趟,搜索數組的n-1個記錄,尋找出最小的(對於整個數組來說則是次小的),然后放置到數組的第1號位置。在第i趟時,搜索數組的n-i+1個記錄,尋找最小的記錄(對於整個數組來說則是第i小的),然后放在數組i-1的位置(注意數組以0起始)。

代碼:

  private static void xuanze()
        {
            int[] a = new int[] { 25, 1, 5, 2, 9, 11, 2, 4 };
            for (int i = 0; i < a.Length - 1; i++)
            {
                int sign = i;
                for (int j = i + 1; j < a.Length; j++)
                {
                    if (a[sign] > a[j])
                    {
                        sign = j;
                    }
                }
                if (sign != i)
                {
                    outPuts(a, "交換之前");//測試使用
                    Console.Write(String.Format("    {0:D2}<-交換->{1:D2}   ", a[i], a[sign]));//測試使用
                    int b = a[i];
                    a[i] = a[sign];
                    a[sign] = b;
                    outPuts(a, "交換之后");//測試使用
                    Console.WriteLine();//測試使用
                }
            }
            Console.WriteLine();//測試使用
            outPuts(a, "最后數據");
            Console.ReadKey();
        }

        private static void outPuts(int[] a, string str)
        {
            Console.Write(str + ":");
            foreach (var item in a)
            {
                Console.Write(item + " ");
            }
        }

 

結果:

 

三、插入排序穩定

插入排序是一種對於有序數列高效的排序。非常聰明的排序。只是對於隨機數列,效率一般,交換的頻率高。

思想:數組下標1開始,和前面下標為0的數據作比較,如果下標0的數據大於(或小於即從大到小排序)下標1的數據則后移,下標1的數據插入到0的位置,否則插入當前位置。數組下標m開始,和所以前面數據作比較,即下標為n(m-1>=n>=0)的數據作比較,如果下標n的數據大於(或小於即從大到小排序)下標m的數據則后移,直到n=-1,將下標為m得到數據插入到下標為0的位置,否則插入當前位置,

代碼:

 private static void charu()
        {
            int[] data = new int[] { 25, 1, 5, 2, 9, 11, 2, 4 };
            for (int i = 1; i < data.Length; i++)
            {
                int d = data[i];
                bool result = true;//標記,表示已經比較到第一個位置
                for (int j = i - 1; j >= 0; j--)
                {
                    if (data[j] > d)
                    {
                        outPuts(data, "移動之前");//測試使用
                        Console.Write(String.Format("    從下標{0}開始  {1}<-移動->{2}   ",i, j, j+1));//測試使用
                        data[j + 1] = data[j];
                        outPuts(data, "移動之后");//測試使用
                        Console.WriteLine();//測試使用
                    }
                    else
                    {
                        outPuts(data, "插入之前");//測試使用
                        Console.Write(String.Format("    從下標{0}開始  數據{1}<-插入到->{2}   ", i, d, j + 1));//測試使用
                        data[j + 1] = d;
                        result = false;
                        outPuts(data, "插入之后");//測試使用
                        Console.WriteLine();//測試使用
                        break;
                    }
                }
                if (result)
                {
                    outPuts(data, "插入之前");//測試使用
                    Console.Write(String.Format("    數據{0}<-插入到->{1}   ", d, 0));//測試使用
                    data[0] = d;
                    outPuts(data, "插入之后");//測試使用
                    Console.WriteLine();//測試使用
                }
            }
            Console.WriteLine();//測試使用
            outPuts(data, "最后數據");
            Console.ReadKey();
        }

        private static void outPuts(int[] a, string str)
        {
            Console.Write(str + ":");
            foreach (var item in a)
            {
                Console.Write(item + " ");
            }
        }

 

 //方法二
private static void charu2(int[] data)
        {
            int j;
            for (int i = 1; i < data.Length; i++)
            {
                int d = data[i];
                for (j = i - 1; j >= 0 && data[j] > d; j--)
                {
                    data[j + 1] = data[j];
                }
                data[j + 1] = d;
            }
        }

 

結果:

 

 

四、快速排序不穩定

快速排序是一種高效排序。它包含了“分而治之”以及“哨兵”的思想。

思想:從數組中挑選一個數(一般為第一個數據)作為“哨兵”,使比它小的放在它的左側,比它大的放在它的右側。

 

 

 說明:25為哨兵,定義變量保存,0位置為待處理位置,從right向前找直到找到比哨兵小的,將其放入到待處理位置,left++,並設置當前為待處理位置,從left開始向后找比哨兵大的放入待處理位置,right--,並設置當前為待處理位置。直到left>right結束,此時第一個哨兵位置確定。並以哨兵為點,將數組分為兩段分別繼續遞歸實現該方法。

代碼:

     private static void kuaisu(int[] data, int left, int right)
        {
            if (left >= right) return;
            int x = data[left];
            int i = left;
            int j = right;
            try
            {
                while (i < j)
                {
                    while (i < j && data[j] >= x)
                    {
                        j--;
                    }
                    if (i == j) break;
                    outPuts(data, "移動之前");//測試使用
                    Console.Write(String.Format("    {0}<-移動到->{1}   ", j, i));//測試使用
                    data[i++] = data[j];
                    outPuts(data, "移動之后");//測試使用
                    Console.WriteLine();//測試使用
                    while (i < j && data[i] <= x)
                    {
                        i++;
                    }
                    if (i == j) break;
                    outPuts(data, "移動之前");//測試使用
                    Console.Write(String.Format("    {0}<-移動到->{1}   ", i, j));//測試使用
                    data[j--] = data[i];
                    outPuts(data, "移動之后");//測試使用
                    Console.WriteLine();//測試使用
                }
                outPuts(data, "移動之前");//測試使用
                Console.Write(String.Format("    數據{0}<-移動到->{1}  數據區間{2}--{3} ", x, i,left,right));//測試使用
                data[i] = x;
                outPuts(data, "移動之后");//測試使用
                Console.WriteLine();//測試使用
                kuaisu(data, left, i - 1);
                kuaisu(data, i + 1, right);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }

        private static void outPuts(int[] a, string str)
        {
            Console.Write(str + ":");
            foreach (var item in a)
            {
                Console.Write(item + " ");
            }
        }

 

結果:

 

五、歸並排序穩定

使用遞歸方法。

思想:將已有序的子序列合並,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。

那么如何確定一個子序列有序呢?當然子序列個數為1時自然有序。所以將數組分為兩段,一直分段遞歸下去,直到數組長度為1返回,此時將其歸並直到結束。

歸並操作的工作原理如下:
第一步:申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合並后的序列
第二步:設定兩個指針,最初位置分別為兩個已經排序序列的起始位置
第三步:比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置
重復步驟3直到某一指針超出序列尾
將另一序列剩下的所有元素直接復制到合並序列尾

代碼:

 

    int[] data = new int[] { 25, 1, 5, 2, 9, 11, 2, 4 };
    guibing(data, 0, data.Length - 1);
    outPuts(data, "最后數據");
    Console.ReadKey();
        private static void guibing(int[] data, int left, int right)
        {
            if (left < right)
            {
                int mid = (left + right) / 2;
                guibing(data, left, mid);
                guibing(data, mid + 1, right);
                arrayAdd(data, left, mid, right);
            }
        }

        private static void arrayAdd(int[] data, int left, int mid, int right)
        {
            int[] d = new int[right - left + 1];
            int i = left;
            int j = mid + 1;
            int k = 0;
            while (i <= mid && j <= right)
            {
                if (data[j] < data[i]) d[k++] = data[j++];
                else d[k++] = data[i++];
            }
            while (i <= mid)
            {
                d[k++] = data[i++];
            }
            while (j <= right)
            {
                d[k++] = data[j++];
            }
            i = left;
            for (k = 0; k < d.Length; k++)
            {
                outPuts(data, "賦值之前");//測試使用
                Console.Write(" ");//測試使用
                data[i++] = d[k];
                outPuts(data, "賦值之后");//測試使用
                Console.Write(String.Format("    賦值下標位置范圍{0}--{1}  臨時數組:",left,right));//測試使用
                foreach (int f in d) //測試使用
                {
                    Console.Write(f + " ");
                }
                Console.WriteLine();//測試使用
            }
        }

        private static void outPuts(int[] a, string str)
        {
            Console.Write(str + ":");
            foreach (var item in a)
            {
                Console.Write(item + " ");
            }
        }

 

結果:

 

 

六、希爾排序不穩定

希爾排序是插入排序的一種更高效的改進版本。

思想:希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序,隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個數列恰被分成一組,算法便終止。

增量算法有:https://blog.csdn.net/foliciatarier/article/details/53891144

本文以:Hibbard 增量序列為例

代碼:

int[] data = new int[] { 25, 1, 5, 2, 9, 11, 2, 4 };
xier(data);
        private static void xier(int[] data)
        {
            List<int> garArry = new List<int>();
            int m = 0;
            while (Math.Pow(2,m) - 1 <= data.Length / 2)
            {
                m++;
                garArry.Add((int)Math.Pow(2, m) - 1);
            }
            for (int y = garArry.Count - 1; y >= 0; y--)
            {
                int gap = garArry[y];
                int j;
                //比較次數
                for (int i = gap; i < data.Length; i++)
                {
                    int d = data[i];
                    for (j = i - gap; j >= 0 && data[j] > d; j -= gap)
                    {
                        outPuts(data, "移動之前");//測試使用
                        Console.Write(String.Format("   {0}<-移動到->{1}  步長{2}  ", j, j + gap, gap));//測試使用
                        data[j + gap] = data[j];
                        outPuts(data, "移動之后");//測試使用
                        Console.WriteLine();//測試使用
                    }
                    outPuts(data, "移動之前");//測試使用
                    Console.Write(String.Format("   數據{0}<-移動到->{1}  步長{2}  ", d, j + gap, gap));//測試使用
                    data[j + gap] = d;
                    outPuts(data, "移動之后");//測試使用
                    Console.WriteLine();//測試使用
                }
            }
            outPuts(data, "最后數據");
        }

        private static void outPuts(int[] a, string str)
        {
            Console.Write(str + ":");
            foreach (var item in a)
            {
                Console.Write(item + " ");
            }
        }

 

結果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM