C#面试题(四) ------ 数据结构常见的八大排序算法

首先来看看排序算法有哪八种:

          

1.直接插入排序

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

         稳定的排序,最坏时间复杂性为O(n^2),空间复杂度为O(1)。

       public void InsertSort<T>(T[] arry,Comparison<T> comparison)
       {
            //直接插入排序是将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的
            for (int i = 1; i < arry.Length; i++)
            {
                T temp = arry[i];
                int j = i - 1;

                // 在已排好序的数列段中找到比新数值小的数值
                while (j >= 0 && comparison(arry[j] , temp) == 1)
                {
                    //将比新数值大的数值向后移
                    arry[j + 1] = arry[j];
                    j--;
                }
                // 如果在已排好序的数列段中找到了比新数值小的数值
                // 将数值替换到此位置
                arry[j + 1] = temp;
            }
        }

 

2.希尔排序(有点疑惑网上的希尔排序都只用三个循环,博主是没有理解透彻,按照原理 应该至少有四个循环,因为插入排序就有两个循环,希望各位看官有知道的能够为博主解惑)

原理 : 将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。

        是非稳定排序算法

       public void ShellSort<T>(T[] arry,Comparison<T> comparison)
       {
            int Length = arry.Length;
            for (int gap = Length / 2; gap > 0; gap = gap / 2) //分组,得到组数
            {
                for (int i = gap; i < gap * 2; i++)//对每一组进行插入排序
                {
                    for (int j = i; j < Length; j += gap)
                    {
                        T temp = arry[j];
                        int k = j - gap;
                        while (k >= 0 && comparison(temp , arry[k]) == -1)
                        {
                            arry[k + gap] = arry[k];
                            k = k - gap;
                        }
                        arry[k + gap] = temp;

                    }
                }
            }
        }

 

3.简单选择排序

原理 : 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

        进行移动操作的时间复杂度为O(n)。进行比较操作的时间复杂度为O(n^2)

    public void SimpleSelectSort<T>( T[] arry , Comparison<T> comparison)
    {
            int k = 0;
            for (int i=0;i<arry.Length;i++)
            {
                k = i;
                for (int j = i + 1;j<arry.Length;j++)
                {
                    if(comparison(arry[k] , arry[j]) == 1)
                    {
                        k = j;
                    }
                }
                Exchange(ref arry[i],ref arry[k]);
            }
        }

4.堆排序

原理 : 是利用一种被称作二叉堆的数据结构进行排序的排序算法。

          二叉堆中,有两个与所维护数组相关的属性。Length表示数组的元素个数,而HeapSize则表示二叉堆中所维护的数组中的元素的个数(并不是数组中的所有元素都一定是二叉堆的有效元素)。因此,根据上述定义有: 0 <= HeapSize <= Length。

         二叉堆可分为最大堆和最小堆两种类型。在最大堆中,二叉树上所有的节点都不大于其父节点,即 A[Parent(i)] >= A[i]。最小堆正好相反:A[Parent(i)] <= A[i]。

         因为在调用MaxHeapify(MinHeapify)方法使根节点为A[i]的二叉堆满足最大(小)堆性质时我们有其左右子堆均已满足最大(小)堆性质这个假设,所以如果我们在将一个待排序的数组构造成最大(小)堆时,需要自底向上地调用 MaxHeapify(MinHeapify)方法。

       在利用最大堆进行排序时,我们先将待排序数组构造成一个最大堆,此时A[0](根节点)则为数组中的最大元素,将A[0]与A[n - 1]交换,则把A[0]放到了最终正确的排序位置。然后通过将HeapSize减去1,将(交换后的)最后一个元素从堆中去掉。然后通过MaxHeapify方法将余下的堆改造成最大堆,然后重复以上的交换。重复这一动作,直到堆中元素只有2个。则最终得到的数组为按照升序排列的数组。

        public void HeapSort<T>(T[] array, Comparison<T> comparison)
        {
            BuildMHeap<T>(array, comparison);
            for (int i = array.Length - 1; i > 0; i--)
            {
                Exchange(ref array[i], ref array[0]);
                MHeapify<T>(array, 0, i, comparison);
            }
        }
        //计算节点的父节点和子节点
        private int Parrent(int i)
        {
            return (i - 1) / 2;
        }
        private int Left(int i)
        {
            return 2 * i + 1;
        }
        private int Right(int i)
        {
            return 2 * i + 2;
        }
        //构建最大堆/最小堆
        private void BuildMHeap<T>(T[] array, Comparison<T> comparison)
        {
            for (int i = array.Length / 2 - 1; i >= 0; i--)
            {
                MHeapify<T>(array, i, array.Length, comparison);
                
            }
        }
        private void MHeapify<T>(T[] array, int i, int heapSize, Comparison<T> comparison)
        {
            int left = Left(i);
            int right = Right(i);

            int extremumIndex = i;
            if (left < heapSize && comparison(array[left], array[i]) > 0)
            {
                extremumIndex = left;
            }

            if (right < heapSize && comparison(array[right], array[extremumIndex]) > 0)
            {
                extremumIndex = right;
            }

            if (extremumIndex != i)
            {
                Exchange<T>(ref array[extremumIndex], ref array[i]);
                MHeapify<T>(array, extremumIndex, heapSize, comparison);
            }
        }

 

5.冒泡排序

原理 : 首先将一个记录的关键字和第二个关键字进行比较,若为逆序,则将两个记录交换,然后比较第2个记录和第3个记录的关键字。依次类推,直至第N-1个记录和第n个记录的关键字进行过比较为止。上述过程称为第一趟冒泡排序,执行n-1次上述过程后,排序完成。

优缺点 :

      优点:稳定 时间复杂度:理想情况下(数组本来就是有序的),此时最好的时间复杂度为o(n),最坏的时间复杂度(数据反序的),此时的时间复杂度为o(n*n) 。  冒泡排序的平均时间负责度为o(n*n).  

       缺点:慢,每次只移动相邻的两个元素。

       public void BubbleSort<T>( T[] array,Comparison<T> comparison)
        {
            for (int i = 0; i < array.Length; i++)
            {
                for (int j = 0; j < array.Length - 1 - i; j++)
                {
                    //比较相邻的两个元素,如果前面的比后面的大,则交换位置
                    if (comparison(array[j] , array[j + 1]) == 1)
                    {
                        Exchange(ref array[j],ref array[j + 1]);
                    }
                }
            }
        }

6.快速排序

     原理 : 快速排序法是采用递归的方式对待排序的数列进行若干次的操作,每次操作使得被操作的数列部分以某个元素为分界值分成两部分,一部分小于该分界值,另一部分大于该分界值.该分界值一般被称为"枢轴". 一般先以左边第一个数作为分界值,将数列按该分界值分成左右两部分,左边部分小于该分界值,右边部分大于该分界值,然后再对左右两部分做重复的操作,直到最后完成排序。

      快速排序是一种不稳定的排序算法

    public void QuickSort<T>(T[] array, Comparison<T> comparison, int left, int right)
    {
            //左边小于右边说明排序还没有完成
            if (left < right)
            {
                T middle = array[(left + right) / 2];
                //注意初始化
                int j = right + 1;
                int i = left - 1;
                while (true)
                {
                    while (comparison(middle, array[++i]) > 0 && i < right) ;//左边,先加的原因是防止找到的最左边会超出界限
                    while (comparison(middle, array[--j]) < 0 && j > 0) ; //右边
                    if (i >= j)
                        break;
                    Exchange<T>(ref array[i], ref array[j]);
                }
                for (int m = 0; m < array.Length; m++)
                {
                    Console.Write(array[m] + " ");
                }
                Console.ReadLine();
                QuickSort<T>(array, comparison, left, i - 1);
                QuickSort<T>(array, comparison, j + 1, right);
            }
        }

7.归并排序

         是分治法(Divide and Conquer)的一个非常典型的应用

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

  public void MergeSortFunction<T>(T[] array, Comparison<T> comparison, int first, int last)
        {
            try
            {
                if (first < last)   //子表的长度大于1,则进入下面的递归处理
                {
                    int mid = (first + last) / 2;   //子表划分的位置
                    MergeSortFunction(array,comparison, first, mid);   //对划分出来的左侧子表进行递归划分
                    MergeSortFunction(array, comparison, mid + 1, last);    //对划分出来的右侧子表进行递归划分
                    MergeSortCore(array, comparison, first, mid, last); //对左右子表进行有序的整合(归并排序的核心部分)
                }
            }
            catch (Exception ex)
            { }
        }

        //归并排序的核心部分:将两个有序的左右子表(以mid区分),合并成一个有序的表
        private void MergeSortCore<T>(T[] array, Comparison<T> comparison, int first, int mid, int last)
        {
            try
            {
                int indexA = first; //左侧子表的起始位置
                int indexB = mid + 1;   //右侧子表的起始位置
                T[] temp = new T[last + 1]; //声明数组(暂存左右子表的所有有序数列):长度等于左右子表的长度之和。
                int tempIndex = 0;
                while (indexA <= mid && indexB <= last) //进行左右子表的遍历,如果其中有一个子表遍历完,则跳出循环
                {
                    if (comparison(array[indexA] , array[indexB]) <= 0) //此时左子表的数 <= 右子表的数
                    {
                        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)
            { }
        }

8.基数排序

        属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,算法的时间复杂度是O(n)

 public static void RadixSort( int[] array, int array_x = 10, int array_y = 100)
        {
            /* 最大数字不超过999999999...(array_x个9) */
            for (int i = 0; i < array_x; i++)
            {
                int[,] bucket = new int[array_x, array_y];
                foreach (var item in array)
                {
                    int temp = (item / (int)Math.Pow(10, i)) % 10;
                    for (int l = 0; l < array_y; l++)
                    {
                        if (bucket[temp, l] == 0)
                        {
                            bucket[temp, l] = item;
                            break;
                        }
                    }
                }
                for (int o = 0, x = 0; x < array_x; x++)
                {
                    for (int y = 0; y < array_y; y++)
                    {
                        if (bucket[x, y] == 0) continue;
                        array[o++] = bucket[x, y];
                    }
                }
            }
        }

辅助函数:

       #region 交换值
        public void Exchange<T>(ref T x, ref T y)
        {
            T temp = x;
            x = y;
            y = temp;
        }
        #endregion
        #region 比较两个int的值
        public int ComparisonInt(int x,int y)
        {
            if(x > y)
            {
                return 1;
            }
            else if(x == y)
            {
                return 0;
            }
            else
            {
                return -1;
            }
        }
        #endregion

源码地址:

           https://github.com/webloverand/Interview

参考列表:

          1.数据结构常见的八大排序算法(详细整理)https://www.cnblogs.com/hokky/p/8529042.html

          2.堆排序——C#实现 : https://blog.csdn.net/zhuo_wp/article/details/78251777

          3.归并排序算法(C#实现): https://www.cnblogs.com/mingmingruyuedlut/archive/2011/08/18/2144984.html

猜你喜欢

转载自blog.csdn.net/dengshunhao/article/details/81040892
今日推荐