算法-排序(C#)

       

排 序
 
1.文件:由一组记录组成,记录有若干数据项组成,唯一标识记录的数据项称关键字;
2.排序是将文件按关键字的递增(减)顺序排列;

3.排序文件中有相同的关键字时,若排序后相对次序保持不变的称稳定排序,否则称不稳定排序;

4.在排序过程中,文件放在内存中处理不涉及数据的内、外存交换的称内排序,反之称外排序;

5.排序算法的基本操作:1)比较关键字的大小;2)改变指向记录的指针或移动记录本身。

6.评价排序方法的标准:1)执行时间;2)所需辅助空间,辅助空间为O(1)称就地排序;另要注意算法的复杂程度。

7.若关键字类型没有比较运算符,可事先定义宏或函数表示比较运算。

8.插入排序
1)直接插入排序
算法中引入监视哨R[0]的作用是:1)保存R[i]的副本;2)简化边界条件,防止循环下标越界。
关键字比较次数最大为(n+2)(n-1)/2;记录移动次数最大为(n+4)(n-1)/2
算法的最好时间是O(n);最坏时间是O(n^2);平均时间是O(n^2);是一种就地的稳定的排序;
2)希尔排序
实现过程:是将直接插入排序的间隔变为dd的取值要注意:1)最后一次必为12)避免d值互为倍数;
关键字比较次数最大为n^1.25;记录移动次数最大为1.6n^1.25
算法的平均时间是O(n^1.25);是一种就地的不稳定的排序;

9.交换排序
1)冒泡排序
实现过程:从下到上相邻两个比较,按小在上原则扫描一次,确定最小值,重复n-1次。
关键字比较次数最小为n-1、最大为n(n-1)/2;记录移动次数最小为0,最大为3n(n-1)/2
算法的最好时间是O(n);最坏时间是O(n^2);平均时间是O(n^2);是一种就地的稳定的排序;
2)快速排序
实现过程:将第一个值作为基准,设置i,j指针交替从两头与基准比较,有交换后,交换jii=j时确定基准,并以其为界限将序列分为两段。重复以上步骤。
关键字比较次数最好为nlog2n+nC(1)、最坏为n(n-1)/2
算法的最好时间是O(nlog2n);最坏时间是O(n^2);平均时间是O(nlog2n);辅助空间为O(log2n);是一种不稳定排序;

10.选择排序
1)直接选择排序
实现过程:选择序列中最小的插入第一位,在剩余的序列中重复上一步,共重复n-1次。
关键字比较次数为n(n-1)/2;记录移动次数最小为0,最大为3(n-1)
算法的最好时间是O(n^2);最坏时间是O(n^2);平均时间是O(n^2);是一种就地的不稳定的排序;
2)堆排序
实现过程:把序列按层次填入完全二叉树,调整位置使双亲大于或小于孩子,建立初始大根或小根堆,调整树根与最后一个叶子的位置,排除该叶子重新调整位置。
算法的最好时间是O(nlog2n);最坏时间是O(nlog2n);平均时间是O(nlog2n);是一种就地的不稳定排序;

11.归并排序
实现过程:将初始序列分为2个一组,最后单数轮空,对每一组排序后作为一个单元,对2个单元排序,直到结束。
算法的最好时间是O(nlog2n);最坏时间是O(nlog2n);平均时间是O(nlog2n);辅助空间为O(n);是一种稳定排序;

12.分配排序
1)箱排序
实现过程:按关键字的取值范围确定箱子的个数,将序列按关键字放入箱中,输出非空箱的关键字。
在桶内分配和收集,及对各桶进行插入排序的时间为O(n),算法的期望时间是O(n),最坏时间是O(n^2)
2)基数排序
实现过程:按基数设置箱子,对关键字从低位到高位依次进行箱排序。
算法的最好时间是O(d*n+d*rd);最坏时间是O(d*n+d*rd);平均时间是O(d*n+d*rd);辅助空间O(n+rd);是一种稳定排序;


         //冒泡排序(从头开始,每一个元素和它的下一个元素比较,如果它大,就将它与比较的元素交换,否则不动。
//                这意味着,大的元素总是在向后慢慢移动直到遇到比它更大的元素。所以每一轮交换完成都能将最大值冒到最后)
        public static void BubbleSort(IList<int> data)
          {
              for (int i = data.Count - 1; i > 0; i--)
              {
                  for (int j = 0; j < i; j++)
                  {
                      if (data[j] > data[j + 1])
                          Swap(data, j, j + 1);
                  }
             }
         }
        //选择排序(选择序列中最小的插入第一位,在剩余的序列中重复上一步,共重复n-1次)
        private static void OrderBy(int[] arr)
        {
            for (int r = 0; r < arr.Length - 1; r++)//0
            {
                int minIndex = r;
                for (int c = r + 1; c < arr.Length; c++)//1 2 3 4
                {
                    if (arr[minIndex] > arr[c])
                    {
                        //记录最小索引
                        minIndex = c;
                    }
                }
                if (minIndex != r)
                {
                    //交换
                    int temp = arr[r];
                    arr[r] = arr[minIndex];
                    arr[minIndex] = temp;
                }
            }
        }
        //插入排序(插入排序是一种对于有序数列高效的排序。非常聪明的排序。只是对于随机数列,效率一般,交换的频率高。)
        //算法思路:
        //⒈ 从第一个元素开始,该元素可以认为已经被排序
        //⒉ 取出下一个元素,在已经排序的元素序列中从后向前扫描
        //⒊ 如果该元素(已排序)大于新元素,将该元素移到下一位置
        //⒋ 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
        //⒌ 将新元素插入到下一位置中
        //⒍ 重复步骤2~5
        private static void InsertSort(int[] arr)
        {
            //插入排序是把无序列的数一个一个插入到有序的数
            //先默认下标为0这个数已经是有序
            for (int i = 1; i < arr.Length; i++)
            {
                int insertVal = arr[i];  //首先记住这个预备要插入的数
                int insertIndex = i - 1; //找出它前一个数的下标(等下 准备插入的数 要跟这个数做比较)
                //如果这个条件满足,说明,我们还没有找到适当的位置
                while (insertIndex >= 0 && insertVal < arr[insertIndex])   //这里小于是升序,大于是降序
                {
                    arr[insertIndex + 1] = arr[insertIndex];   //同时把比插入数要大的数往后移
                    insertIndex--;      //指针继续往后移,等下插入的数也要跟这个指针指向的数做比较        
                }
                //插入(这时候给insertVal找到适当位置)
                arr[insertIndex + 1] = insertVal;
            }
        }
        //快速排序(从数列中挑选一个数作为“哨兵”,使比它小的放在它的左侧,比它大的放在它的右侧。将要排序是数列递归地分割到
        //          最小数列,每次都让分割出的数列符合“哨兵”的规则,自然就将数列变得有序)
        //具体过程:
        //设序列为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。
        public static void QuickSortStrict(IList<int> data, int low, int high)
         {
            if (low >= high) return;
            int temp = data[low];
            int i = low + 1, j = high;
            while (true)
             {
                 while (data[j] > temp) j--;
                 while (data[i] < temp && i < j) i++;
                 if (i >= j) break;
                 Swap(data, i, j);
                 i++; j--;
             }
             if (j != low)
                 Swap(data, low, j);
             QuickSortStrict(data, j + 1, high);
             QuickSortStrict(data, low, j - 1);
         }
        private static void Swap(IList<int> data, int i, int j)
        {
            var temp = data[i];
            data[i] = data[j];
            data[j] = temp;
        }
        //归并排序(将两个有序的数列,通过比较,合并为一个有序数列)
        //过程解析:将数列分为两部分,分别得到两部分数列的有序版本,然后逐个比较,将比较出的小数逐个放进
        //          新的空数列中。当一个数列放完后,将另一个数列剩余数全部放进去
        public static IList<int> MergeSort(IList<int> data, int low, int high)
         {
             int length = high - low + 1;
             IList<int> mergeData = new int[length];
             if (low == high)
             {
                 mergeData[0] = data[low];
                 return mergeData;
             }
             int mid = (low + high) / 2;
             IList<int> leftData = MergeSort(data, low, mid);
             IList<int> rightData = MergeSort(data, mid + 1, high);
             int i = 0, j = 0;
             while (true)
             {
                 if (leftData[i] < rightData[j])
                 {
                     mergeData[i + j] = leftData[i++]; //不能使用Add,Array Length不可变
                     if (i == leftData.Count)
                     {
                         int rightLeft = rightData.Count - j;
                         for (int m = 0; m < rightLeft; m++)
                         {
                             mergeData[i + j] = rightData[j++];
                         }
                         break;
                     }
                 }
                 else
                 {
                     mergeData[i + j] = rightData[j++];
                     if (j == rightData.Count)
                     {
                         int leftleft = leftData.Count - i;
                         for (int n = 0; n < leftleft; n++)
                         {
                             mergeData[i + j] = leftData[i++];
                         }
                         break;
                     }
                 }
             }
             return mergeData;
 
         }
        //堆排序(堆的特性:父节点的值总是小于(或大于)它的子节点。近似二叉树)
        //原理:将数列构建为最大堆数列(即父节点总是最大值),将最大值(即根节点)交换到数列末尾
        //      这样要排序的数列数总和减少,同时根节点不再是最大值,调整最大堆数列。如此重复,最后得到有序数列
        //实现准备:如何将数列构造为堆——父节点i的左子节点为2i+1,右子节点为2i+2。节点i的父节点为floor((i-1)/2)
        /// <summary>  
        /// 堆排序方法。  
        /// </summary>  
        /// <param name="a">  
        /// 待排序数组。  
        /// </param>  
        private static void HeapSort(int[] a)
        {
            BuildMaxHeap(a); // 建立大根堆。  
            for (int i = a.Length - 1; i > 0; i--)
            {
                Swap(a,0,i); // 将堆顶元素和无序区的最后一个元素交换。  
                MaxHeaping(a, 0, i); // 将新的无序区调整为大根堆。  
                // 打印每一次堆排序迭代后的大根堆。  
                for (int j = 0; j < i; j++)
                {
                    Console.Write(a[j] + " ");
                }
                Console.WriteLine(string.Empty);
            }
        }
        /// <summary>  
        /// 由底向上建堆。由完全二叉树的性质可知,叶子结点是从index=a.Length/2开始,
        /// 所以从index=(a.Length/2)-1结点开始由底向上进行大根堆的调整。  
        /// </summary>  
        /// <param name="a">  
        /// 待排序数组。  
        /// </param>  
        private static void BuildMaxHeap(int[] a)
        {
            for (int i = (a.Length / 2) - 1; i >= 0; i--)
            {
                MaxHeaping(a, i, a.Length);
            }
        }
        /// <summary>  
        /// 将指定的结点调整为堆。  
        /// </summary>  
        /// <param name="a">  
        /// 待排序数组。  
        /// </param>  
        /// <param name="i">  
        /// 需要调整的结点。  
        /// </param>  
        /// <param name="heapSize">  
        /// 堆的大小,也指数组中无序区的长度。  
        /// </param>  
        private static void MaxHeaping(int[] a, int i, int heapSize)
        {
            int left = (2 * i) + 1; // 左子结点。  
            int right = 2 * (i + 1); // 右子结点。  
            int large = i; // 临时变量,存放大的结点值。  
            // 比较左子结点。  
            if (left < heapSize && a[left] > a[large])
            {
                large = left;
            }
            // 比较右子结点。  
            if (right < heapSize && a[right] > a[large])
            {
                large = right;
            }
            // 如有子结点大于自身就交换,使大的元素上移;并且把该大的元素调整为堆以保证堆的性质。  
            if (i != large)
            {
                Swap(a,i,large);
                MaxHeaping(a, large, heapSize);
            }
        }  
        //希尔排序(希尔排序是插入排序的一种更高效的改进版本。
        //在前面介绍的插入排序,我们知道
        //1.它对有序数列排序的效率是非常高的
        //2.要排序的数向前移动是一步步进行的导致插入排序效率低。
        //希尔排序正是利用第一点,改善第二点,达到更理想的效果)
        //原理:通过奇妙的步长,插入排序间隔步长的元素,随后逐渐缩短步长至1,实现数列的插入排序
        //过程解析:采用的步长是N/2,每次取半,直至1。循环内部就是标准的插入排序
        public static void ShellSort(IList<int> data)
         {
             int temp;
             for (int gap = data.Count / 2; gap > 0; gap /= 2)
             {
                 for (int i = gap; i < data.Count; i += gap)
                 {
                     temp = data[i];
                     for (int j = i - gap; j >= 0; j -= gap)
                     {
                         if (data[j] > temp)
                         {
                             data[j + gap] = data[j];
                             if (j == 0)
                             {
                                 data[j] = temp;
                                 break;
                             }
                         }
                         else
                         {
                             data[j + gap] = temp;
                             break;
                         }
                     }
                 }
             }
         }

13.各种内部排序方法的比较和选择:
1)按平均时间复杂度分为:
1) 平方阶排序:直接插入、直接选择、冒泡排序;
2) 线性对数阶:快速排序、堆排序、归并排序;
3) 指数阶:希尔排序;
4) 线性阶:箱排序、基数排序。

2)选择合适排序方法的因素:
1)待排序的记录数;2)记录的大小;3)关键字的结构和初始状态;4)对稳定性的要求;
5)语言工具的条件;6)存储结构;  7)时间和辅助空间复杂度。

3)结论:
1) 若规模较小可采用直接插入或直接选择排序;
2) 若文件初始状态基本有序可采用直接插入、冒泡或随机快速排序;
3) 若规模较大可采用快速排序、堆排序或归并排序;
4) 任何借助于比较的排序,至少需要O(nlog2n)的时间,箱排序和基数排序只适用于有明显结构特征的关键字;
5) 有的语言没有提供指针及递归,使归并、快速、基数排序算法复杂;
6) 记录规模较大时为避免大量移动记录可用链表作为存储结构,如插入、归并、基数排序,但快速、堆排序在链表上难以实现,可提取关键字建立索引表,然后对索引表排序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值