排序算法总结(c#版)

算法质量的衡量标准:

1:时间复杂度:分析关键字比较次数和记录的移动次数;

2:空间复杂度:需要的辅助内存;

3:稳定性:相同的关键字计算后,次序是否不变。

排序的分类

1:选择排序(直接选择排序、堆排序)

2:交换排序(冒泡排序、快速排序)

3:插入排序(直接插入排序、折半插入排序、Shell排序)

4:归并排序

5:捅式排序

6:基数排序

1:直接选择排序

思路:

第一次从R[0]~R[n-1]中选取最小值,与R[0]交换,第二次从R{1}~R[n-1]中选取最小值,与R[1]交换,...., 第i次从R[i-1]~R[n-1]中选取最小值,与R[i-1]交换,.....,第n-1次从R[n-2]~R[n-1]中选取最小值,与R[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。

效率分析:

在直接选择排序中,共需要进行n-1次选择和交换,每次选择需要进行 n-i 次比较 (1<=i<=n-1),而每次交换最多需要3次移动,因此,总的比较次数C=1/2(n*n - n),

总的移动次数 3(n-1).由此可知,直接选择排序的时间复杂度为 O(n2) (n的平方),所以当记录占用字节数较多时,通常比直接插入排序的执行速度快些。

由于在直接选择排序中存在着不相邻元素之间的互换,因此,直接选择排序是一种不稳定的排序方法。

 

代码:

private static void SelectSort(int[] data)
        {
            int arrayLength = data.Length;

            //依次进行n-1次比较,第i次比较将第i大的值选出放在i的位置上
            for (int i = 0; i < arrayLength - 1; i++)
            {
                //保留最小值的索引
                int minIndex = i;
                //将第i个数据和它后面的数据比较
                for (int j = i + 1; j < arrayLength; j++)
                { 
                    if (data[minIndex] > data[j])
                    {
                        minIndex = j;
                    }
                }

                //交换数据
                if (minIndex != i)
                {
                    int temp = data[i];
                    data[i] = data[minIndex];
                    data[minIndex] = temp;
                }
            }
        }


 

 

2:堆排序

 

思路:

堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。

用大根堆排序的基本思想

  ① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区

  ② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key

  ③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。

  ……

  直到无序区只有一个元素为止。

 

效率分析:

排序的时间,主要由建立初始]堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。

  堆排序的最坏时间复杂度为O(nlogn)。堆序的平均性能较接近于最坏性能。

  由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。

  堆排序是就地排序,辅助空间为O(1),

  它是不稳定的排序方法。

 

代码:

private static void HeapSort(int[] data)
        {
            int arrayLength = data.Length;

            //建堆
            for (int i = 0; i < arrayLength - 1; i++)
            { 
                //建堆
                BuildMaxdHeap(data, arrayLength - 1 - i);
                //交换堆顶和最后一个元素
                Swap(data, 0, arrayLength - 1 - i); 
            }
        }
        private static void BuildMaxdHeap(int[] data, int lastIndex)
        {
            //从最后一个节点的父节点开始
            for (int i = (lastIndex - 1) / 2; i >= 0; i--)
            { 
                //当前位置
                int k = i;
                //如果当前k节点存在子节点
                while (k * 2 + 1 <= lastIndex)
                {
                    int biggerIndex = k * 2 + 1;
                    //判断右子节点是否存在
                    if (biggerIndex < lastIndex)
                    { 
                        if (data[biggerIndex] < data[biggerIndex + 1])
                            biggerIndex++;
                    }
                    if (data[k] < data[biggerIndex])
                    { 
                        Swap(data, k, biggerIndex);
                        k = biggerIndex;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
        private static void Swap(int[] data, int i, int j)
        {
            int temp = data[i];
            data[i] = data[j];
            data[j] = temp;
        }

 

3:冒泡排序

 

思路:

次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。至此第一趟结束,将最大的数放到了最后。在第二趟:仍从第一对数开始比较(因为可能由于第2个数和第3个数的交换,使得第1个数不再小于第2个数),将小数放前,大数放后,一直比较到倒数第二个数(倒数第一的位置上已经是最大的),第二趟结束,在倒数第二的位置上得到一个新的最大数(其实在整个数列中是第二大的数)。如此下去,重复以上过程,直至最终完成排序。

 

效率分析:

若记录序列的初始状态为"正序",则冒泡排序过程只需进行一趟排序,在排序过程中只需进行n-1次比较,且不移动记录;反之,若记录序列的初始状态为"逆序",则需进行n(n-1)/2次比较和记录移动。因此冒泡排序总的时间复杂度为O(n*n)。

 

代码:

private static void BubbleSort(int[] data)
        {
            int arrayLength = data.Length;

            for (int i = 0; i < arrayLength - 1; i++)
            {
                bool flag = false;
                for (int j = 0; j < arrayLength - 1 - i; j++)
                {
                    if (data[j] > data[j + 1])
                    {
                        int temp = data[j];
                        data[j] = data[j + 1];
                        data[j + 1] = temp;
                        flag = true;
                    }
                }

                if (!flag)
                {
                    break;
                }
            }
        }

 

4:快速排序

思路:

1)设置两个变量I、J,排序开始的时候:I=0,J=N-1;

  2)以第一个数组元素作为关键数据,赋值给key,即 key=A[0];

  3)从J开始向前搜索,即由后开始向前搜索(J=J-1即J--),找到第一个小于key的值A[j],A[j]与A[i]交换;

  4)从I开始向后搜索,即由前开始向后搜索(I=I+1即I++),找到第一个大于key的A[i],A[i]与A[j]交换;

  5)重复第3、4、5步,直到 I=J; (3,4步是在程序中没找到时候j=j-1,i=i+1,直至找到为止。找到并交换的时候i, j指针位置不变。另外当i=j这过程一定正好是i+或j-完成的最后令循环结束。)

 

效率分析:

冒泡排序的一种改进

 

代码:

 

private static void QuickSort(int[] data)
        {
            //调用子排序
            SubSort(data, 0, data.Length - 1);
        }
        //对data数组中start到end范围内的子序列进行排序
        private static void SubSort(int[] data, int start, int end)
        {
            if (start < end)
            {
                //第一个元素作为分界值
                int middle = data[start];
                int i = start;
                int j = end + 1;
                
                while (true)
                {
                    while (i < end && data[++i] <= middle) ;
                    while (j > start && data[--j] >= middle) ;
                    if (i < j)
                    {
                        Swap(data, i, j);
                    }
                    else
                    {
                        break;
                    }
                }

                Swap(data, start, j);
                //递归进行左子排序
                SubSort(data, start, j - 1);
                //递归进行右子排序
                SubSort(data, j + 1, end);
            }
        }


5:直接插入排序

思路:

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

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

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

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

 

效率分析:

速度慢,空间效率高,稳定。

 

代码:

private static void InsertSort(int[] data)
        {
            for (int i = 1; i < data.Length; i++)
            {
                //备份data[i]的值
                int temp = data[i];

                if (data[i] < data[i - 1])
                {
                    int j = i - 1;

                    //通过比较找出要插入的位置,同时将大的数值向右移动
                    for (; j >= 0 && data[j] > temp; j--)
                    {
                        data[j + 1] = data[j];
                    }

                    //将temp插入合适的位置
                    data[j + 1] = temp;
                }
            }
        }


 

6:折半插入排序

 

思路:

折半插入排序(binary insertion sort)是对插入排序算法的一种改进,由于排序算法过程中,就是不断的依次将元素插入前面已排好序的序列中。由于前半部分为已排好序的数列,这样我们不用按顺序依次寻找插入点,可以采用折半查找的方法来加快寻找插入点的速度。

折半插入排序算法的具体操作为:在将一个新元素插入已排好序的数组的过程中,寻找插入点时,将待插入区域的首元素设置为a[low],末元素设置为a[high],则轮比较时将待插入元素与a[m],其中m=(low+high)/2相比较,如果比参考元素小,则选择a[low]到a[m-1]为新的插入区域(即high=m-1),否则选择a[m+1]到a[high]为新的插入区域(即low=m+1),如此直至low<=high不成立,即将此位置之后所有元素后移一位,并将新元素插入a[high+1]。

 

效率分析:

折半插入排序算法是一种稳定的排序算法,比直接插入算法明显减少了关键字之间比较的次数,因此速度比直接插入排序算法快,但记录移动的次数没有变,所以折半插入排序算法的时间复杂度仍然为O(n^2),与直接插入排序算法相同。

 

代码:

private static void BinaryInsertSort(int[] data)
        {
            for (int i = 1; i < data.Length; i++)
            {
                //备份data[i]的值
                int temp = data[i];
                int low = 0;
                int high = i - 1;

                while (low <= high)
                {
                    //取low到high中间的索引
                    int middle = (low + high) / 2;

                    //确定temp在中间值的哪侧
                    if (temp > data[middle])
                    {
                        low = middle + 1;
                    }
                    else
                    {
                        high = middle - 1;
                    }
                }

                //将low到i处的所有元素向后整体移一位
                for (int j = i; j > low; j--)
                {
                    data[j] = data[j - 1];
                }

                //将temp插入合适的位置
                data[low] = temp;
            }
        }


 

7:Shell排序

 

思路:

先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插人排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<;…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。

 

效率分析:

性能由于直接插入排序,不稳定

 

代码:

private static void ShellSort(int[] data)
        {
            int arrayLength = data.Length;

            //可增变量
            int h = 1;
            while (h <= arrayLength / 3)
            {
                h = h * 3 + 1;
            }

            while (h > 0)
            {
                for (int i = h; i < arrayLength; i++)
                {
                    //备份当前值
                    int temp = data[i];

                    if (data[i] < data[i - h])
                    {
                        int j = i - h;
                        //整体向后移动h格
                        for (; j >= 0 && data[j] > temp; j -= h)
                        {
                            data[j + h] = data[j];
                        }
                        data[j + h] = temp;
                    }
                }

                h = (h - 1) / 3;
            }
        }

 

8:归并排序

思路:

申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

  设定两个指针,最初位置分别为两个已经排序序列的起始位置

  比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

  重复步骤3直到某一指针达到序列尾

  将另一序列剩下的所有元素直接复制到合并序列尾

 

效率分析:

速度仅次于快速排序,但较稳定

 

代码:

private static void MergeSort(int[] data)
        {
            Sort(data, 0, data.Length - 1);    
        }
        //将left到right范围内的数组进行归并排序
        private static void Sort(int[] data, int left, int right)
        {
            if (left < right)
            {
                int center = (left + right) / 2;
                //对左侧数组递归排序
                Sort(data, left, center);
                //对右侧数组递归排序
                Sort(data, center + 1, right);
                //合并
                Merge(data, left, center, right);
            }
        }
        //将两个数组进行归并
        private static void Merge(int[] data, int left, int center, int right)
        {
            int[] tempArray = new int[data.Length];
            int middle = center + 1;
            int third = left;
            int temp = left;

            while (left <= center && middle <= right)
            {
                //从两个数组中取出小的放入中间的数组
                if (data[left] <= data[middle])
                {
                    tempArray[third++] = data[left++];
                }
                else
                {
                    tempArray[third++] = data[middle++];
                }
            }

            //将余下的依次放入中间数组
            while (middle <= right)
            {
                tempArray[third++] = data[middle++];
            }
            while (left <= center)
            {
                tempArray[third++] = data[left++];
            }

            //将中间数组中的内容复制到原数组
            while (temp <= right)
            {
                data[temp] = tempArray[temp++];
            }
        }

9:捅式排序

 

思路:

 

效率分析:

 

代码:

private static void BucketSort(int[] data, int min, int max)
        {
            int arrayLength = data.Length;
            int[] temp = new int[arrayLength];
            
            //记录待排序元素的信息
            int[] buckets = new int[max - min];

            //计算每个元素在序列中出现的次数
            for (int i = 0; i < arrayLength; i++)
            {
                buckets[data[i] - min]++;
            }

            //计算“落入”各捅内的元素在有序序列中的位置
            for (int i = 1; i < max - min; i++)
            {
                buckets[i] = buckets[i] + buckets[i - 1];
            }
            
            //将当前数组备份到temp中
            data.CopyTo(temp, 0);
            
            //根据buckets数组中的信息将待排序的各元素让入相应的位置
            for (int k = arrayLength - 1; k >= 0; k--)
            {
                data[--buckets[temp[k] - min]] = temp[k];
            }
        }

 

10:基数排序

 

思路:

将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。

 

效率分析:

时间效率:设待排序列为n个记录,d个关键码,关键码的取值范围为radix,则进行链式基数排序的时间复杂度为O(d(n+radix)),其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(n),共进行d趟分配和收集。 空间效率:需要2*radix个指向队列的辅助空间,以及用于静态链表的n个指针。

 

代码:

/*
         * radix 指定关键字拆分的进制,如10为十进制
         * d 指定关键字拆分成几个子关键字
         */
        private static void RadixSort(int[] data, int radix, int d)
        {
            int arrayLength = data.Length;
            int[] temp = new int[arrayLength];
            int[] buckets = new int[radix];

            //依次从最高位的子关键字对待排数据进行排序
            for (int i = 0, rate = 1; i < d; i++)
            {
                //重置临时数组
                Array.Clear(buckets, 0, arrayLength);
                //将data数组进行备份
                data.CopyTo(temp, 0);
                //计算每个待排序数据的子关键字
                for (int j = 0; j < arrayLength; j++)
                {
                    int subKey = (temp[j] / rate) % radix;
                    buckets[subKey]++;
                }

                for (int j = 1; j < radix; j++)
                {
                    buckets[j] = buckets[j] + buckets[j - 1];
                }

                //按照子关键字对指定数据进行排序
                for (int m = arrayLength - 1; m >= 0; m--)
                {
                    int subKey = (temp[m] / rate) % radix;
                    data[--buckets[subKey]] = temp[m];
                }

                rate *= radix;
            }
        }


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值