12种排序


插入排序

它通过创建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

通常步骤:
1.从第一个元素开始,该元素可以认为已经被排序。
2.取出下一个元素,在已经排序的元素序列中从后向前扫描。
3.如果该元素(已排序)大于新元素,将该元素移到下一位置。
4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。
5.将新元素插入到该位置后。
6.重复步骤2~5。

void InsertionSort(int[] num, int first, int last)
    {
        int i, j;
        int temp;
        for (i = first + 1; i <= last; i++)
        {
            temp = num[i];
            j = i - 1;

            //与已排序的数逐一比较,大于temp时,该数后移
            //当first=0,j循环到-1时,由于[[短路求值]],不会运算array[-1]
            while ((j >= first) && (num[j] > temp))  
            {
                num[j + 1] = num[j];
                j--;
            }
            num[j + 1] = temp;      //被排序数放到正确的位置

        }
    }

二分插入排序

它在直接插入排序算法上进行小的改动。它与直接排序算法最大的区别在于查找插入位置时使用的是二分查找的方式,在速度上有一定的提升。

通常步骤:
1.从第一个元素开始,该元素可以认为已经被排序。
2.取出下一个元素,在已经排序的元素序列中二分查找到第一个比它大的数的位置。
3.将新元素插入到该位置后。
4.重复上述两步。

void BinInsertSort(int[] a, int n)
    {
        int key, left, right, middle;
        
        for (int i = 1; i < n; i++)
        {
            key = a[i];
            left = 0;
            right = i - 1;
            
            while (left <= right)
            {
                middle = (left + right) / 2;
                if (a[middle] > key)
                    right = middle - 1;
                else
                    left = middle + 1;
            }

            for (int j = i - 1; j >= left; j--)
            {
                a[j + 1] = a[j];
            }
            
            a[left] = key;
        }
    }

希尔排序

它也可以称为递减增量排序算法,是插入排序的一种高速而稳定的改进版本。

通常步骤:
1.先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。
2.所有距离为d1的倍数的记录放在同一个组中,在各组内进行直接插入排序。
3.取第二个增量d2<d1重复上述的分组和排序。
4.直至所取的增量dt=1,即所有记录放在同一组中进行直接插入排序为止。

void ShellSort(int[] num)
    {
        const int n = num.Length;
        int i, j, temp;
        int gap = 0;
        int[] a = num;
        while (gap <= n)
        {
            gap = gap * 3 + 1;
        }
        while (gap > 0)
        {
            for (i = gap; i < n; i++)
            {
                j = i - gap;
                temp = a[i];
                while ((j >= 0) && (a[j] > temp))
                {
                    a[j + gap] = a[j];
                    j = j - gap;
                }
                a[j + gap] = temp;
            }
            gap = (gap - 1) / 3;
        }
    }

选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

通常步骤:
n个记录的文件的直接选择排序可经过n-1趟直接选择排序得到有序结果

1.初始状态:无序区为R[1…n],有序区为空。
2.第i趟排序开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
3.第n-1趟结束,数组有序化了。

void SelectionSort(int[] a, int len)
    {
        int i, j, min, t;
        for (i = 0; i < len - 1; i++)
        {
            min = i;
            //查找最小值
            for (j = i + 1; j < len; j++)
                if (a[min] > a[j])
                    min = j;
            //交换
            if (min != i)
            {
                t = a[min];
                a[min] = a[i];
                a[i] = t;
            }
        }
    }

冒泡排序

它重复地走访要排序的数列,比较相邻的两个数,如果前者比后者大,则进行交换,每一轮排序后最后一个元素必然是最大的数。

通常步骤:
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

void BubbleSort(int[] arr, int count)
    {
        int i = count, j;
        int temp;

        while (i > 0)
        {
            for (j = 0; j < i - 1; j++)
            {
                if (arr[j] > arr[j + 1])
                {
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
            i--;
        }
    }

双向冒泡排序

又称之为"鸡尾酒排序"

它是冒泡排序的一种变形,不同的地方在于从低到高然后从高到低。它的效能比冒泡排序好一点。

通常步骤:
1.依次比较相邻的两个数,将小的数放在前面,大的数放在后面。
2.第一趟可得到:将最大数放到最后一位。
3.第二趟可得到:将第二大的数放到倒数第二位。
4.如此下去,重复以上过程,直至最终完成排序。

void CocktailSort(int[] a, int nsize)
    {
        int tail = nsize - 1, i, j;
        for (i = 0; i < tail;)
        {
            for (j = tail; j > i; --j) //第一轮,先将最小的数据排到前面
            {
                if (a[j] < a[j - 1])
                {
                    int temp = a[j];
                    a[j] = a[j - 1];
                    a[j - 1] = temp;
                }
            }
            ++i;                    //原来i处数据已排好序,加1
            for (j = i; j < tail; ++j)    //第二轮,将最大的数据排到后面
            {
                if (a[j] > a[j + 1])
                {
                    int temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                }
            }
            tail--;                 //原tail处数据也已排好序,将其减1
        }
    }

快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。其基本思想是基本思想是,通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

通常步骤:
快速排序使用分治法来把一个串分为两个子串行。

1.从数列中挑出一个元素,称为 “基准”。
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3.递归得把小于基准值元素的子数列和大于基准值元素的子数列排序。

//(基准书为每组第一个数)
    void QuickSort(int[] arr, int left, int right)
    {
        int i, j, v;
        if (left < right)
        {
            i = left; j = right;
            v = arr[i]; //准备以本次最左边的元素值为标准进行划分,先保存其值
            do
            {
                while (arr[j] > v && i < j)
                    j--;        //从右向左找第1个小于标准值的位置j
                if (i < j)                               //找到了,位置为j
                {
                    arr[i] = arr[j];
                    i++;
                }           //将第j个元素置于左端并重置i
                while (arr[i] < v && i < j)
                    i++;      //从左向右找第1个大于标准值的位置i
                if (i < j)                       //找到了,位置为i
                {
                    arr[j] = arr[i];
                    j--;
                }           //将第i个元素置于右端并重置j
            } while (i != j);
            arr[i] = v;         //将标准值放入它的最终位置,本次划分结束
            QuickSort(arr, left, i - 1);     //对标准值左半部递归调用本函数
            QuickSort(arr, i + 1, right);    //对标准值右半部递归调用本函数
        }

    }


堆排序

它是利用堆这种数据结构所设计的一种排序算法。

堆调整:
为了保持堆的特性而做的一个操作。对某一个节点为根的子树做堆调整,将该根节点进行“下沉”操作(通过和子节点交换完成),一直下沉到合适的位置,使得刚才的子树满足堆的性质。

 /*返回父节点*/
    int parent(int i)
    {
        return (int)Mathf.Floor((i - 1) / 2);
    }

    /*返回左孩子节点*/
    int left(int i)
    {
        return (2 * i + 1);
    }

    /*返回右孩子节点*/
    int right(int i)
    {
        return (2 * i + 2);
    }

    /*对以某一节点为根的子树做堆调整(保证最大堆性质)*/
    void HeapAdjust(int[] A, int i, int heap_size)
    {
        int l = left(i);
        int r = right(i);
        int largest;
        int temp;
        if (l < heap_size && A[l] > A[i])
        {
            largest = l;
        }
        else
        {
            largest = i;
        }
        if (r < heap_size && A[r] > A[largest])
        {
            largest = r;
        }
        if (largest != i)
        {
            temp = A[i];
            A[i] = A[largest];
            A[largest] = temp;
            HeapAdjust(A, largest, heap_size);
        }
    }

    /*建立最大堆*/
    void BuildHeap(int[] A, int heap_size)
    {
        for (int i = (heap_size - 2) / 2; i >= 0; i--)
        {
            HeapAdjust(A, i, heap_size);
        }
    }

    void HeapSort(int[] A, int heap_size)
    {
        BuildHeap(A, heap_size);
        int temp;
        for (int i = heap_size - 1; i >= 0; i--)
        {
            temp = A[0];
            A[0] = A[i];
            A[i] = temp;
            HeapAdjust(A, 0, i);
        }
    }

归并排序

它是建立在归并操作的一种排序算法。是采用分治法的一个典型应用。归并排序是一种稳定的排序方法。

将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

通常步骤:
递归版本
1.把长度为n的输入序列分成两个长度为n/2的子序列。
2.对这两个子序列分别采用归并排序。
3.将两个排序好的子序列合并成一个最终的排序序列。

//将二个有序数列a[first...mid]和a[mid...last]合并。
    void MergeSort(int[] a, int first, int mid, int last, int[] temp)
    {
        int i = first, j = mid + 1;
        int m = mid, n = last;
        int k = 0;
        while (i <= m && j <= n)
        {
            if (a[i] <= a[j])
                temp[k++] = a[i++];
            else
                temp[k++] = a[j++];
        }
        while (i <= m)
            temp[k++] = a[i++];
        while (j <= n)
            temp[k++] = a[j++];
        for (i = 0; i < k; i++)
            a[first + i] = temp[i];
    }
    
    //递归地完成归并排序
    void MergeSort(int[] a, int first, int last, int[] temp)
    {
        if (first < last)
        {
            int mid = (first + last) / 2;
            MergeSort(a, first, mid, temp);    //左边有序
            MergeSort(a, mid + 1, last, temp); //右边有序
            MergeSort(a, first, mid, last, temp); //再将二个有序数列合并
        }
    }

桶排序

或称为箱排序,它是将数组分到有限数量的桶里。每个桶再分别排序(可以再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。

它是稳定的,且在大多数常见排序里是最快的一种,但是非常耗费空间,基本上是最耗空间的一种排序算法,而且只能在某些情形下使用。

通常步骤:
1.设置一个定量的数组当作空桶。
2.寻访串行,并且把项目一个一个放到对应的桶去。
3.对每个不是空的桶进行排序。
4.从不是空的桶里把项目再放回原来的串行中。

 public static void BucketSort(double[] array, int bucketNum)
    {
        //创建bucket时,在二维中增加一组标识位,其中bucket[x, 0]表示这一维所包含的数字的个数
        //通过这样的技巧可以少写很多代码
        double[,] bucket = new double[bucketNum, array.Length + 1];
        foreach (var num in array)
        {
            int bit = (int)(10 * num);
            bucket[bit, (int)++bucket[bit, 0]] = num;
        }
        //为桶里的每一行使用插入排序
        for (int j = 0; j < bucketNum; j++)
        {
            //为桶里的行创建新的数组后使用插入排序
            double[] insertion = new double[(int)bucket[j, 0]];
            for (int k = 0; k < insertion.Length; k++)
            {
                insertion[k] = bucket[j, k + 1];
            }
            //插入排序
            StraightInsertionSort(insertion);
            //把排好序的结果回写到桶里
            for (int k = 0; k < insertion.Length; k++)
            {
                bucket[j, k + 1] = insertion[k];
            }
        }
        //将所有桶里的数据回写到原数组中
        for (int count = 0, j = 0; j < bucketNum; j++)
        {
            for (int k = 1; k <= bucket[j, 0]; k++)
            {
                array[count++] = bucket[j, k];
            }
        }
    }

    public static void StraightInsertionSort(double[] array)
    {
        //插入排序
        for (int i = 1; i < array.Length; i++)
        {
            double sentinel = array[i];
            int j = i - 1;
            while (j >= 0 && sentinel < array[j])
            {
                array[j + 1] = array[j];
                j--;
            }
            array[j + 1] = sentinel;
        }
    }

计数排序

它使用一个额外的数组 M,其中第 i 个元素是待排序数组 A 中值等于 i 的元素的个数。然后根据数组 M 来将 A 中的元素排到正确的位置,它只能对整数进行排序。

通常步骤:
1.找出待排序的数组中最大和最小的元素
2.统计数组中每个值为i的元素出现的次数,存入数组C的第i项
3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

public static void CountingSort(int[] array)
    {
        if (array.Length == 0) return;
        int min = array[0];
        int max = min;
        foreach (int number in array)
        {
            if (number > max)
            {
                max = number;
            }
            else if (number < min)
            {
                min = number;
            }
        }
        int[] counting = new int[max - min + 1];
        for (int i = 0; i < array.Length; i++)
        {
            counting[array[i] - min] += 1;
        }
        int index = -1;
        for (int i = 0; i < counting.Length; i++)
        {
            for (int j = 0; j < counting[i]; j++)
            {
                index++;
                array[index] = i + min;
            }
        }
    }

基数排序

它是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

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

int[] RadixInOut(int[] arr, int len, int digit)
    {
        //定义十个桶,每个桶也是一个数组,
        //使用交叉数组定义,当后面出现桶的序号再实例化,而不是一开始用二维数组[10, len]定义,减少内存占用
        int[][] temp = new int[10][];

        //将arr中的元素值依次放入对应桶中
        for (int i = 0; i < len; i++)
        {
            //个位数:a/1%10;十位数:a/10%10;百位数:a/100%10……则中间的digit=10^n决定取哪一位
            int n = (int)(arr[i] / digit % 10);
            //将arr[i]放入对应的空桶temp[n]中
            //第一次出现某个位数值,实例化空桶temp[n],并将arr[i]放入空桶
            if (temp[n] == null)
            {
                //不知道到底该位数值有多少个,因此长度设置为原数组长度
                temp[n] = new int[len];
                //初始化数组值为一定不存在的值
                for (int j = 0; j < len; j++)
                {
                    temp[n][j] = -1;
                }
                temp[n][0] = arr[i];
            }
            else
            {
                //temp[n]已存在时循环temp[n],找到值不为-1的索引,将arr[i]放入对应位置
                int j = 0;
                while (temp[n][j] != -1)
                {
                    j++;
                }
                temp[n][j] = arr[i];
            }
        }


        //将桶内元素依次放回到数组中
        for (int i = 0, k = 0; i < 10; i++)
        {
            if (temp[i] != null)
            {
                int j = 0;
                while (temp[i][j] != -1)
                {
                    arr[k++] = temp[i][j++];
                }
            }
        }
        return arr;
    }

    //对数字进行排序,不考虑负数,不考虑小数点
    public void RadixSort(int[] arr)
    {
        int len = arr.Length;
        if (len < 2)
        {
            return;
        }

        //获取数组中最大的位数
        int digit = 0;
        for (int i = 0; i < len; i++)
        {
            int this_digit = ((int)arr[i]).ToString().Length;
            if (this_digit > digit)
            {
                digit = this_digit;
            }
        }

        //依次进行个位排序,十位排序,……
        for (int i = 0; i < digit; i++)
        {
            //个位数:a/1%10;十位数:a/10%10;百位数:a/100%10……则中间的10的n次方决定取哪一位
            int cal_digit = (int)Mathf.Pow(10, i);
            arr = RadixInOut(arr, len, cal_digit);
        }
    }


学习博客地址


时间复杂度,空间复杂度统计:

猜你喜欢

转载自blog.csdn.net/THIOUSTHIOUS/article/details/88618022