转载 C#排序算法

转载 http://www.codeisbug.com/Doc/6

- 希尔排序

希尔排序是将组分段,进行插入排序.
对想提高C#语言编程能力的朋友,我们可以互相探讨一下。
如:下面的程序,并没有实现多态,来,帮它实现一下。

using System;
public class ShellSorter
{
    public void Sort(int[] list)
    {
        int inc;
        for (inc = 1; inc <= list.Length / 9; inc = 3 * inc + 1) ;
        for (; inc > 0; inc /= 3)
        {
            for (int i = inc + 1; i <= list.Length; i += inc)
            {
                int t = list[i - 1];
                int j = i;
                while ((j > inc) && (list[j - inc - 1] > t))
                {
                    list[j - 1] = list[j - inc - 1];
                    j -= inc;
                }
                list[j - 1] = t;
            }
        }
    }
}
public class MainClass
{
    public static void Main()
    {
        int[] iArrary = new int[] { 1, 5, 3, 6, 10, 55, 9, 2, 87, 12, 34, 75, 33, 47 };
        ShellSorter sh = new ShellSorter();
        sh.Sort(iArrary);
        for (int m = 0; m <= 13; m++)
            Console.WriteLine("{0}", iArrary[m]);
    }
}

- 插入排序

输入一个元素,检查数组列表中的每个元素,将其插入到一个已经排好序的数列中的适当位置,使数列依然有序,当最后一个元素放入合适位置时,该数组排序完毕。

using System;
public class InsertionSorter
{
    public void Sort(int[] list)
    {
        for (int i = 1; i < list.Length; ++i)
        {
            int t = list[i];
            int j = i;
            while ((j > 0) && (list[j - 1] > t))
            {
                list[j] = list[j - 1];
                --j;
            }
            list[j] = t;
        }
    }
}
public class MainClass
{
    public static void Main()
    {
        int[] iArrary = new int[] { 1, 5, 3, 6, 10, 55, 9, 2, 87, 12, 34, 75, 33, 47 };
        InsertionSorter ii = new InsertionSorter();
        ii.Sort(iArrary);
        for (int m = 0; m <= 13; m++)
            Console.WriteLine("{0}", iArrary[m]);
    }
}

- 选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法(比如序列[5, 5, 3]第一次就将第一个[5]与[3]交换,导致第一个5挪动到第二个5后面)。

选择排序是不稳定的。算法复杂度O(n2)–[n的平方]

using System;
public class SelectionSorter
{
    // public enum comp {COMP_LESS,COMP_EQUAL,COMP_GRTR};
    private int min;
    // private int m=0;
    public void Sort(int[] list)
    {
        for (int i = 0; i < list.Length - 1; ++i)
        {
            min = i;
            for (int j = i + 1; j < list.Length; ++j)
            {
                if (list[j] < list[min])
                    min = j;
            }
            int t = list[min];
            list[min] = list[i];
            list[i] = t;
            // Console.WriteLine("{0}",list[i]);
        }
 
    }
}
public class MainClass
{
    public static void Main()
    {
        int[] iArrary = new int[] { 1, 5, 3, 6, 10, 55, 9, 2, 87, 12, 34, 75, 33, 47 };
        SelectionSorter ss = new SelectionSorter();
        ss.Sort(iArrary);
        for (int m = 0; m <= 13; m++)
            Console.WriteLine("{0}", iArrary[m]);
 
    }
}
  • 直接插入排序
    直接插入排序(straight insertion sort)的作法是:

每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。

第一趟比较前两个数,然后把第二个数按大小插入到有序表中; 第二趟把第三个数据与前两个数从后向前扫描,把第三个数按大小插入到有序表中;依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。

直接插入排序属于稳定的排序,时间复杂性为o(n^2),空间复杂度为O(1)。

直接插入排序是由两层嵌套循环组成的。外层循环标识并决定待比较的数值。内层循环为待比较数值确定其最终位置。直接插入排序是将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的。当前一数值比待比较数值大的情况下继续循环比较,直到找到比待比较数值小的并将待比较数值置入其后一位置,结束该次循环。

值得注意的是,我们必需用一个存储空间来保存当前待比较的数值,因为当一趟比较完成时,我们要将待比较数值置入比它小的数值的后一位 插入排序类似玩牌时整理手中纸牌的过程。插入排序的基本方法是:每步将一个待排序的记录按其关键字的大小插到前面已经排序的序列中的适当位置,直到全部记录插入完毕为止。

 第一层循环是为了依次将数组中的值放入到有序表里,这里循环是从1开始,第1个元素就是有序表。比如循环进到了5,那么前五个元素就是有序表,后面的就是无序表
 第二层循环是为了形成有序表,第一次外层循环,都有一个元素放到有序表中,并形成新的有序表。
C#代码:(我写的不是很简练)
public class SortHelper
    {
        public static void InsertSort<T>(T[] array) where T : IComparable
        {
            int length = array.Length;
            for (int i = 1; i < length; i++)
            {
                T temp = array[i];
                if (temp.CompareTo(array[i - 1])<0)
                {
                    for (int j = 0; j < i; j++)
                    {
                        if (temp.CompareTo(array[j])<0)
                        {
                            temp = array[j];
                            array[j] = array[i];
                            array[i] = temp;
                        }
                    }
                }
            }
        }
    }

int[] array = new int[] {23,15,27,90,69,66,158,45,32,1};
            Console.WriteLine("before insert sort");
            foreach (int i in array)
            {
                Console.Write(i+"->");
            }
            Console.WriteLine();
            SortHelper.InsertSort<int>(array);
            Console.WriteLine("after insert sort");
            foreach (int i in array)
            {
                Console.Write(i + "->");
            }
            Console.WriteLine();
            Console.Read();
输出
before insert sort
23->15->27->90->69->66->158->45->32->1->
after insert sort
1->15->23->27->32->45->66->69->90->158->

- 冒泡排序算法

冒泡排序 作为一种渐进复杂度O(N^2)的排序方法, 实质上属于一种形式“玄妙的”选择排序,只是相比选择排序多了一些无意义的交换。它比选择排序更难于理解和解释(就在于它多次无意义的交换的干扰,不信你讲给楼下阿姨比较下),又不具有插入排序的“在线”优点,跟极易理解的基数排序更不能在思维难度上相比,就算同快排,归并相比,也具有复杂度高得多的劣势。那么为什么冒泡排序会作为经典,成为初学者学习语言的遇到的首个算法?

int temp;
            int[] arrSort = new int[] { 10, 8, 3, 5, 6, 7, 9 };
            for (int i = 0; i < arrSort.Length; i++)
            {
                for (int j = i + 1; j < arrSort.Length; j++)
                {
                    if (arrSort[j] < arrSort[i])
                    {
                        temp = arrSort[j];
                        arrSort[j] = arrSort[i];
                        arrSort[i] = temp;
                    }
                }
}

冒泡排序并不是一种有工程意义的排序算法。不如说std::sort以外的也许都没有工程意义……
但是讲冒泡排序可以带出几个问题:
冒泡排序只有一个基本操作就是比较和交换相邻的两个元素,可以体现将一个复杂的问题转化为非常简单的操作的叠加的思想
冒泡排序看上去每一次循环都差不多,为什么可以在有限次循环之内结束,如何分析算法的正确性,这是一个不错的数学问题
分析复杂度简单,一眼就看得出来是O(n^2)。代码也的确是最短的。边界条件很少。甚至于多循环几遍问题也不大。
分析完冒泡排序会发现,其他O(n2)的算法的原理都跟冒泡排序有共通之处:用O(n)的时间排出一个元素。所以讲其他O(n2)排序就不用花太多时间解释了。
嘛其实哪个都不是什么关键问题,其实也许最大的原因是因为这么讲没啥毛病,所以也没必要改吧……对于初学者来说,写一个冒泡排序写对的概率其实要比其他O(n^2)的算法高不少。

另外冒泡排序有个小优点是很容易展开成没有循环的形式,而且代码也不会太难看,比如说我现在固定要排的是四个数,就可以写:
#define cs(a,b) (if(a>b){int t; t = a; a = b; b = t;})
cs(a,b);cs(b,c);cs(c,d);cs(a,b);cs(b,c);cs(a,b);
用别的排序就不太可能了。虽然现在编译器都可以自动循环展开了,但以前并不是这样,而且这样小清新的代码多好啊

  • 快速排序
    算法思想简单描述:
    快速排序是对冒泡排序的一种本质改进。它的基本思想是通过一趟
    扫描后,使得排序序列的长度能大幅度地减少。在冒泡排序中,一次
    扫描只能确保最大数值的数移到正确位置,而待排序序列的长度可能只
    减少1。快速排序通过一趟扫描,就能确保某个数(以它为基准点吧)
    的左边各数都比它小,右边各数都比它大。然后又用同样的方法处理
    它左右两边的数,直到基准点的左右只有一个元素为止。它是由
    C.A.R.Hoare于1962年提出的。

显然快速排序可以用递归实现,当然也可以用栈化解递归实现。下面的
函数是用递归实现的,有兴趣的朋友可以改成非递归的。

快速排序是不稳定的。最理想情况算法时间复杂度O(nlog2n),最坏O(n2)

想到了快速排序,于是自己就用C#实现了快速排序的算法:
快速排序的基本思想:
分治法,即,分解,求解,组合 .

分解:
在 无序区R[low…high]中任选一个记录作为基准(通常选第一个记录,并记为keyValue,其下标为keyValuePosition),以此为基准划分成两个较小的 子区间R[low,keyValuePosition- 1]和R[keyValuePosition+ 1 , high],并使左边子区间的所有记录均小于等于基准记录,右边子区间的所有记录均大于等于基准记录,基准记录无需参加后续的排序。而划分的关键是要求出 基准记录所在的位置keyValuePosition.

求解:
通过递归调用快速排序对左、右子区间R[low…keyValuePosition-1]和R[keyValuePosition+1…high]快速排序

组合:
当"求解"步骤中的两个递归调用结束时,其左、右两个子区间已有序。对快速排序而言,"组合"步骤无须做什么,可看作是空操作。

具体过程:
设序列为R[low,high],从其中选第一个为基准,设为keyValue,然后设两个指针i和j,分别指向序列R[low,high]的起始和结束位置上:
1),将i逐渐增大,直到找到大于keyValue的关键字为止;
2),将j逐渐减少,直到找到小于等于keyValue的关键字为止;
3),如果i<j,即R[i,j]的元素数大于1,则交换R[i]和R[j];
4),将基准记录keyValue放到合适的位置上,即i和j同时指向的位置(或者同时指向的位置-1),则此位置为新的keyValuePosition。

备注:
快速排序是不稳定排序,即相同的关键字排序后,相对位置是不确定的。
下面是我的C#实现的代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace QuickSort
{
    class QuickSort
    {
        static void Main(string[] args)
        {
            //声明数据进行相应的测试
            int[] myArray = new int[]{45, 36, 18, 53, 72, 30, 48, 93, 15, 36};
            //myArray = new int[] { 3, 4, 5, 1};
            //myArray = new int[] { 3, 4, 2, 1};
 
            int lowIndex = 0;                                       //数组的起始位置(从0开始)
            int highIndex = myArray.Length - 1;         //数组的终止位置
 
            //快速排序
            QuickSortFunction(myArray, lowIndex, highIndex);
 
            //输出排完之后的数组
            for (int i = 0; i < myArray.Length; i++)
            {
                Console.WriteLine(myArray[i].ToString());
            }
        }
 
        //快速排序(目标数组,数组的起始位置,数组的终止位置)
        private static void QuickSortFunction(int[] array, int low, int high)
        {
            try
            {
                int keyValuePosition;   //记录关键值的下标
 
                //当传递的目标数组含有两个以上的元素时,进行递归调用。(即:当传递的目标数组只含有一个元素时,此趟排序结束)
                if (low < high) 
                {
                    keyValuePosition = keyValuePositionFunction(array, low, high);  //获取关键值的下标(快排的核心)
 
                    QuickSortFunction(array, low, keyValuePosition - 1);    //递归调用,快排划分出来的左区间
                    QuickSortFunction(array, keyValuePosition + 1, high);   //递归调用,快排划分出来的右区间
                }
            }
            catch (Exception ex)
            { }
        }
 
        //快速排序的核心部分:确定关键值在数组中的位置,以此将数组划分成左右两区间,关键值游离在外。(返回关键值应在数组中的下标)
        private static int keyValuePositionFunction(int[] array, int low, int high)
        {
            int leftIndex = low;        //记录目标数组的起始位置(后续动态的左侧下标)
            int rightIndex = high;      //记录目标数组的结束位置(后续动态的右侧下标)
 
            int keyValue = array[low];  //数组的第一个元素作为关键值
            int temp;
 
            //当 (左侧动态下标 == 右侧动态下标) 时跳出循环
            while (leftIndex < rightIndex)
            {
                while (leftIndex < rightIndex && array[leftIndex] <= keyValue)  //左侧动态下标逐渐增加,直至找到大于keyValue的下标
                {
                    leftIndex++;
                }
                while (leftIndex < rightIndex && array[rightIndex] > keyValue)  //右侧动态下标逐渐减小,直至找到小于或等于keyValue的下标
                {
                    rightIndex--;
                }
                if(leftIndex < rightIndex)  //如果leftIndex < rightIndex,则交换左右动态下标所指定的值;当leftIndex==rightIndex时,跳出整个循环
                {
                    temp = array[leftIndex];
                    array[leftIndex] = array[rightIndex];
                    array[rightIndex] = temp;
                }
            }
 
            //当左右两个动态下标相等时(即:左右下标指向同一个位置),此时便可以确定keyValue的准确位置
            temp = keyValue;
            if (temp < array[rightIndex])   //当keyValue < 左右下标同时指向的值,将keyValue与rightIndex - 1指向的值交换,并返回rightIndex - 1
            {
                array[low] = array[rightIndex - 1];
                array[rightIndex - 1] = temp;
                return rightIndex - 1;
            }
            else //当keyValue >= 左右下标同时指向的值,将keyValue与rightIndex指向的值交换,并返回rightIndex
            {
                array[low] = array[rightIndex];
                array[rightIndex] = temp;
                return rightIndex;
            }
        }
    }
}

- 堆排序

功能:堆排序
输入:数组名称(也就是数组首地址)、数组中元素个数
算法思想简单描述:
堆排序是一种树形选择排序,是对直接选择排序的有效改进。
堆的定义如下:具有n个元素的序列(h1,h2,…,hn),当且仅当
满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,…,n/2)
时称之为堆。在这里只讨论满足前者条件的堆。

由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项。完全二叉树可以
很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。
初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储顺序,
使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点
交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点
的堆,并对它们作交换,最后得到有n个节点的有序序列。

从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素
交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数
实现排序的函数。

堆排序是不稳定的。算法时间复杂度O(nlog2n)。

//堆排序算法(传递待排数组名,即:数组的地址。故形参数组的各种操作反应到实参数组上)
     private static void HeapSortFunction(int[] array)
        {
            try
            {
                BuildMaxHeap(array);    //创建大顶推(初始状态看做:整体无序)
         for (int i = array.Length -1; i >0; i--)
                {
                    Swap(ref array[0], ref array[i]); //将堆顶元素依次与无序区的最后一位交换(使堆顶元素进入有序区)
                    MaxHeapify(array, 0, i); //重新将无序区调整为大顶堆
                }
            }
            catch (Exception ex)
            { }
        }
 
        ///<summary>
        /// 创建大顶推(根节点大于左右子节点)
        ///</summary>
        ///<param name="array">待排数组</param>
     private static void BuildMaxHeap(int[] array)
        {
            try
            {
                //根据大顶堆的性质可知:数组的前半段的元素为根节点,其余元素都为叶节点
         for (int i = array.Length /2-1; i >=0; i--) //从最底层的最后一个根节点开始进行大顶推的调整
                {
                    MaxHeapify(array, i, array.Length); //调整大顶堆
                }
            }
            catch (Exception ex)
            { }
        }
 
        ///<summary>
        /// 大顶推的调整过程
        ///</summary>
        ///<param name="array">待调整的数组</param>
        ///<param name="currentIndex">待调整元素在数组中的位置(即:根节点)</param>
        ///<param name="heapSize">堆中所有元素的个数</param>
     private static void MaxHeapify(int[] array, int currentIndex, int heapSize)
        {
            try
            {
                int left =2* currentIndex +1;    //左子节点在数组中的位置
         int right =2* currentIndex +2;   //右子节点在数组中的位置
         int large = currentIndex;   //记录此根节点、左子节点、右子节点 三者中最大值的位置
 
                if (left < heapSize && array[left] > array[large])  //与左子节点进行比较
                {
                    large = left;
                }
                if (right < heapSize && array[right] > array[large])    //与右子节点进行比较
                {
                    large = right;
                }
                if (currentIndex != large)  //如果 currentIndex != large 则表明 large 发生变化(即:左右子节点中有大于根节点的情况)
                {
                    Swap(ref array[currentIndex], ref array[large]);    //将左右节点中的大者与根节点进行交换(即:实现局部大顶堆)
                    MaxHeapify(array, large, heapSize); //以上次调整动作的large位置(为此次调整的根节点位置),进行递归调整
                }
            }
            catch (Exception ex)
            { }
        }
 
        ///<summary>
        /// 交换函数
        ///</summary>
        ///<param name="a">元素a</param>
        ///<param name="b">元素b</param>
     private static void Swap(refint a, refint b)
        {
            int temp =0;
            temp = a;
            a = b;
            b = temp;
        }

- 归并排序

归并排序(Merge Sort)是利用"归并"技术来进行排序。归并是指将若干个已排序的子文件合并成一个有序的文件。归并排序有两种方式:1): 自底向上的方法 2):自顶向下的方法

1、 自底向上的方法
(1) 自底向上的基本思想
自底向上的基本思想是:第1趟归并排序时,将待排序的文件R[1…n]看作是n个长度为1的有序子文件,将这些子文件两两归并,若n为偶数,则得到n/2个长度为2的有序子文件;若n为奇数,则最后一个子文件轮空(不参与归并)。故本趟归并完成后,前n/2 - 1个有序子文件长度为2,但最后一个子文件长度仍为1;第2趟归并则是将第1趟归并所得到的n/2个有序的子文件两两归并,如此反复,直到最后得到一个长度为n的有序文件为止。
上述的每次归并操作,均是将两个有序的子文件合并成一个有序的子文件,故称其为"二路归并排序"。类似地有k(k>2)路归并排序。

2、自顶向下的方法(本文主要介绍此种方法,下面的文字都是对此种方法的解读)

(1) 自顶向下的基本思想
采用分治法进行自顶向下的算法设计,形式更为简洁。
自顶向下的归并排序:是利用递归和分而治之的技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并成为越来越大的有序序列,归并排序包括两个步骤,分别为:

  1)划分子表

  2)合并半子表

(1)分治法的三个步骤
设归并排序的当前区间是R[low…high],分治法的三个步骤是:
①分解:将当前区间一分为二,即求分裂点
②求解:递归地对两个子区间R[low…mid]和R[mid+1…high]进行归并排序;
③组合:将已排序的两个子区间R[low…mid]和R[mid+1…high]归并为一个有序的区间R[low…high]。
递归的终结条件:子区间长度为1(一个记录自然有序)。

如下演示递归的整个过程:

递归便是深度遍历(如下由左至右进行遍历):假设有这样的一列数组{9,8,7,6,5,4,3,2,1}进行划分的顺序如下:

{9,8,7,6,5,4,3,2,1} --> {9,8,7,6,5},{4,3,2,1}

{9,8,7,6,5} --> {9,8,7},{6,5}

                    {9,8,7} --> {9,8},{7}

                                      {9,8} --> {9},{8}

                    {6,5} -->{6},{5}

{4,3,2,1} --> {4,3},{2,1}

                  {4,3} -->{4},{3}

                  {2,1} -->{2},{1}

当深度划分到左右数组都只剩1个元素的时候,进行上述逆序的合并:

{9},{8} --> {8,9} 然后和 {7} --> {7,8,9}

                            {6},{5} --> {5,6}    然后 {7,8,9}和{5,6} --> {5,6,7,8,9}

                                 {2},{1} --> {1,2}

                                 {4},{3} --> {3,4}   然后 {1,2}和 {3,4} --> {1,2,3,4}

                                                                                                                     最终{5,6,7,8,9}和{1,2,3,4} --> {1,2,3,4,5,6,7,8,9}

具体实现代码如下所示:

//归并排序(目标数组,子表的起始位置,子表的终止位置)
        private static void MergeSortFunction(int[] array, int first, int last)
        {
            try
            {
                if (first < last)   //子表的长度大于1,则进入下面的递归处理
                {
                    int mid = (first + last) / 2;   //子表划分的位置
                    MergeSortFunction(array, first, mid);   //对划分出来的左侧子表进行递归划分
                    MergeSortFunction(array, mid + 1, last);    //对划分出来的右侧子表进行递归划分
                    MergeSortCore(array, first, mid, last); //对左右子表进行有序的整合(归并排序的核心部分)
                }
            }
            catch (Exception ex)
            { }
        }
 
        //归并排序的核心部分:将两个有序的左右子表(以mid区分),合并成一个有序的表
        private static void MergeSortCore(int[] array, int first, int mid, int last)
        {
            try
            {
                int indexA = first; //左侧子表的起始位置
                int indexB = mid + 1;   //右侧子表的起始位置
                int[] temp = new int[last + 1]; //声明数组(暂存左右子表的所有有序数列):长度等于左右子表的长度之和。
                int tempIndex = 0;
                while (indexA <= mid && indexB <= last) //进行左右子表的遍历,如果其中有一个子表遍历完,则跳出循环
                {
                    if (array[indexA] <= array[indexB]) //此时左子表的数 <= 右子表的数
                    {
                        temp[tempIndex++] = array[indexA++];    //将左子表的数放入暂存数组中,遍历左子表下标++
                    }
                    else//此时左子表的数 > 右子表的数
                    {
                        temp[tempIndex++] = array[indexB++];    //将右子表的数放入暂存数组中,遍历右子表下标++
                    }
                }
                //有一侧子表遍历完后,跳出循环,将另外一侧子表剩下的数一次放入暂存数组中(有序)
                while (indexA <= mid)
                {
                    temp[tempIndex++] = array[indexA++];
                }
                while (indexB <= last)
                {
                    temp[tempIndex++] = array[indexB++];
                }
 
                //将暂存数组中有序的数列写入目标数组的制定位置,使进行归并的数组段有序
                tempIndex = 0;
                for (int i = first; i <= last; i++)
                {
                    array[i] = temp[tempIndex++];
                }
            }
            catch (Exception ex)
            { }
        }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值