• 小朋友学数据结构(4):归并排序


    小朋友学数据结构(4):归并排序

    (一)基本思想

    归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

     
    7-1.jpg

    (二)代码实现

    import java.util.Arrays;
    
    public class Sort {
    
        public static void mergeSort(int[] array) {
            sort(array, 0, array.length - 1);
        }
    
        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[] tmpArr = new int[data.length];
            int centerNext = center + 1;
            // 记录临时数组的索引
            int tmp = left;
            int index = left;
            while (left <= center && centerNext <= right) {
                //从两个数组中取出最小的放入临时数组
                if (data[left] <= data[centerNext]) {
                    tmpArr[tmp++] = data[left++];
                } else {
                    tmpArr[tmp++] = data[centerNext++];
                }
            }
    
            // 若右边数组还有剩余元素,把这些剩余元素依次放入临时数组
            while (centerNext <= right) {
                tmpArr[tmp++] = data[centerNext++];
            }
    
            // 若左边数组还有剩余元素,把这些剩余元素依次放入临时数组
            while (left <= center) {
                tmpArr[tmp++] = data[left++];
            }
    
            // 将临时数组中的内容复制回原数组
            while (index <= right) {
                data[index] = tmpArr[index++]; 
            }
        }
    
        public static void main(String[] args) {
            int[] arr = {57, 68, 59, 52, 72, 28, 96, 33};
            System.out.println("Original array: " + Arrays.toString(arr));  
            mergeSort(arr);
            System.out.println("Sorted array: " + Arrays.toString(arr));   
        }
    }
    

    运行结果:

    > javac -encoding utf-8 Sort.java
    > java Sort
    
    Original array: [57, 68, 59, 52, 72, 28, 96, 33]
    Sorted array: [28, 33, 52, 57, 59, 68, 72, 96]
    

    (三)程序分析

    咱们用数组data[8] = [57, 68, 59, 52, 72, 28, 96, 33]来分析一下上面程序的执行过程。
    1 sort方法的具体执行情况
    调用mergeSort()方法会调用sort(data, 0, 7)。

    sort()内部用了递归,第一次被调用时包含了3步:
    sort(data, 0, 3)
    sort(data, 4, 7)
    merge(data, 0, 3, 7)

    将递归展开,可进一步细分为7步:
    sort(data, 0, 1)
    sort(data, 2, 3)
    merge(data, 0, 1, 3)
    sort(data, 4, 5)
    sort(data, 6, 7)
    merge(data, 4, 5, 7)
    merge(data, 0, 3, 7)

    再次将递归展开,进一步细分为15步
    sort(data, 0, 0)
    sort(data, 1, 1)
    merge(data, 0, 0, 1)
    sort(data, 2, 2)
    sort(data, 3, 3)
    merge(data, 2, 2, 3)
    merge(data, 0, 1, 3)
    sort(data, 4, 4)
    sort(data, 5, 5)
    merge(data, 4, 4, 5)
    sort(data, 6, 6)
    sort(data, 7, 7)
    merge(data, 6, 6, 7)
    merge(data, 4, 5, 7)
    merge(data, 0, 3, 7)

    sort里面只有在left<right时才会执行,所以上面的8个sort都不执行。
    相当于上面的15步只剩下7步:
    merge(data, 0, 0, 1)
    merge(data, 2, 2, 3)
    merge(data, 0, 1, 3)
    merge(data, 4, 4, 5)
    merge(data, 6, 6, 7)
    merge(data, 4, 5, 7)
    merge(data, 0, 3, 7)

    2 merge方法的具体执行情况
    (1) merge(data, 0, 0, 1)
    tmpArr[] = new int[8], centerNext = 1, tmp = 0, index = 0

    第一个while循环:
    0<=0 && 1<=1 成立
    data[left]=data[0]=57
    data[centerNext]=data[1]=68
    if条件成立,执行tmpArr[0]=data[0]=57
    tmp自加变成1,left自加变成1,
    此时while条件left <= center不再成立,循环结束。

    第二个while循环:
    centerNext=1,right= 1, centerNext<=right成立
    tmpArr[tmp]=tmpArr[1]=data[1]=68
    tmp自加变成2,centerNext自加变成2,
    此时while条件centerNext<=right不再成立,循环结束。

    第三个while循环:
    left=1, center=0,left <= center不成立,循环不执行。

    第四个while循环:
    把tmpArr中的数据逐个赋值给data
    data[0] = tmpArr[0] = 57,index自加变为1
    data[1] = tmpArr[1] = 68,index自加变为2,循环结束

    这样,就达到了data[0]与data[1]排序的目的,这里因为data[0]和data[1]本来就是有序的,看不出效果。下一个merge立马就能看出效果。

    (2) merge(data, 2, 2, 3)
    tmpArr[] = new int[8], centerNext = 3, tmp = 2, index = 2

    第一个while循环:
    2<=2 && 3<=3 成立
    data[left] = data[2] = 59
    data[centerNext] = data[3] = 52
    if条件不成立,执行else语句
    tmpArr[2] = data[3] = 52
    tmp自加变成3,centerNext自加变成4
    此时while条件centerNext <= right不再成立,循环结束

    第二个while循环:
    centerNext = 4,right= 3, centerNext <= right 不成立,循环结束

    第三个while循环:
    left = 2, center = 2,left <= center成立,tmpArr[3] = data[2] = 59
    tmp自加变为4,left自加变为3,此时left <= center不成立,循环结束

    第四个while循环:
    作用是把tmpArr中的数据逐个赋值给data
    index = 2, right = 3, index <= right成立
    data[index] = data[2] = tmpArr[index] = tmpArr[2] = 52
    index自加变为3,此时index <= right仍然成立
    data[index] = data[3] = tmpArr[index] = tmpArr[3] = 59
    index自加变为4,此时index <= right不成立,循环结束。

    注意,现在data变为[57,68, 52, 59, 72, 28, 96, 33]
    可以观察到,data[0]和data[1]是按顺序排列的,data[2]和data[3]也是按顺序排列的(原先这俩是反序排列)。

    (3)merge(data, 0, 1, 3)
    这一步的作用,是把data里的元素从第0个到第3个(共4个元素),进行归并排序。

    第一个while循环:
    tmpArr[] = new int[8], left = 0, center = 1, centerNext = 2, right = 3, tmp = 0, index = 0
    循环条件left <= center && centerNext <= right成立,进入循环
    if条件data[0] = 57, data[2] = 52, data[0] <= data[2]不成立,
    执行else语句tmpArr[0] = data[2] = 52
    tmp自加变成1,centerNext 自加变成3

    循环条件left <= center && centerNext <= right仍然成立,再次进入循环
    if语句中data[left] = data[0] = 57, data[centerNext] = data[3] = 59,57 <= 59成立,
    执行if语句tmpArr[1] = data[0] = 57,tmp自加变成2,left自加变成1

    循环条件 left <= center && centerNext <= right第三次成立,第三次进入循环
    if语句中,data[left] = data[1] = 68, data[centerNext] = data[3] = 59, 68 < 59不成立
    执行else语句tmpArr[2] = data[3] = 59, tmp自加变为3,centerNext自加变成4

    此时循环条件left <= center && centerNext <= right不成立,循环结束

    第二个while循环:
    centerNext = 4, right = 3, 4 <= 3不成立,循环不执行。

    第三个while循环:
    left = 1, center = 1, 1 <= 1,循环条件成立
    tmpArr[3] = data[1] = 68,
    tmp自加后变为4,left自加后变为2,此时循环条件不成立,循环结束

    第四个while循环
    index = 0, right = 3, data[0] = tmpArr[0] = 52
    index自加变为1,data[1] = tmpArr[1] = 57
    index自加变为2,data[2] = tmpArr[2] = 59
    index自加变为3,data[3] = tmpArr[3] = 68
    index自加变为4,index <= right不成立,循环结束

    至此,data[] = {52, 57, 59, 68, 72, 28, 96, 33}

    (4)merge(data, 4, 4, 5)
    这一步是在前三次的mege结果的基础上,对第4个和第5个元素进行排序,结果为
    data[] = {52, 57, 59, 68, 28, 72, 96, 33}

    (5)merge(data, 6, 6, 7)
    这一步是在前四次的mege结果的基础上,对第6个和第7个元素进行排序,结果为
    data[] = {52, 57, 59, 68, 28, 72, 33, 96}

    (6)merge(data, 4, 5, 7)
    这一步是在前五次的mege结果的基础上,对第4个至第7个元素进行排序,结果为
    data[] = {52, 57, 59, 68, 28, 33, 72, 96}

    (7)merge(data, 0, 3, 7)
    这一步是在前五次的mege结果的基础上,对第4个至第7个元素进行排序,结果为
    data[] = {28, 33, 52, 57, 59, 68, 72, 96}
    至此,data数组的8个元素已排序完毕。



    分类:
    1)插入排序(直接插入排序、希尔排序)
    2)交换排序(冒泡排序、快速排序)
    3)选择排序(直接选择排序、堆排序)
    4)归并排序
    5)分配排序(基数排序)

     
    sort.jpg

    一、直接插入排序

    (一)基本思想

    在要排序的一组数中,假设前面(n-1)[n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。

     
    1-1.jpg

    (二)C语言代码实现

    #include<stdio.h>
    
    
    void insertSort(int a[], int n)
    {
        int i, j, temp;
        for (i = 1; i < n; i++)
        {
            temp = a[i];
            j = i;
    
            // 将大于temp的值整体后移一个单位
            while(j > 0 && a[j-1] > temp)
            {
                a[j] = a[j-1];
                j--;
            }
            a[j]=temp;
        }
    }
    
    
    int main()
    {
        int arr[] = {57, 68, 59, 52};
        int len = sizeof(arr) / sizeof(int);
        insertSort(arr, len);
        int i = 0;
        for(; i < len; i++)
        {
            printf("%d ", arr[i]);
        }
    
        return 0;
    }
    

    运行结果:

    52, 57, 59, 68
    

    程序分析:
    for循环中,
    (1) i = 1, temp = a[1] = 68, j = 1, a[0] = 57, a[0] > temp不成立,不需要调整

    (2)i = 2,temp = a[2] = 59,
    ① j = 2,a[1] = 68 > temp,执行循环a[2] = a[1] = 68,j自减。
    ② j = 1, a[0] = 57 > temp不成立,循环结束。
    ③ 最后执行a[1] = temp = 59,此时arr = {57,59,68,52}

    (3)i = 3,temp = a[3] = 52
    ① j = 3, a[2] = 68 > temp,执行循环a[3] = a[2] = 68,j自减
    ② j = 2,a[1] = 59 > temp,执行循环a[2] = a[1] = 59,j自减
    ③ j = 1,a[0] = 57 > temo,执行循环a[1] = a[0] = 57,j自减后变为0,循环结束
    ④ 最后执行a[0] = temp = 52,此时a= {52, 57, 59, 68}

    二、希尔排序

    (一)基本思想

    希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

    (二)例子

    有一个数组,其原始数组为:

     
    2-1.png

    取初始增量gap = length / 2 = 5,这样就将整个数组分为5组(每组用相同的颜色表示)

     
    2-2.png

    将这5组的数据分别按由小到大的顺序排列,结果为

     
    2-3.png

    缩小增量gap = gap / 2 = 2,整个数组被分成两组

     
    2-4.png

    将这两组的数据分别按由小到大的顺序排列,结果为

     
    2-5.png

    再次缩小增量,整个数组被分为1组

     
    2-6.png

    将这组数据按从小到大的顺序排序,最终结果为

     
    2-7.png

    (三)代码

    1 C语言实现

    #include<stdio.h>
    
    void swap(int &a, int &b)
    {
        a ^= b;
        b ^= a;
        a ^= b;
    }
    
    void shellsort(int a[], int n)
    {
        int i, j, gap;
        for (gap = n / 2; gap > 0; gap /= 2)
        {
            for (i = gap; i < n; i++)
            {
                for (j = i - gap; j >= 0 && a[j] > a[j + gap]; j -= gap)
                {
                    swap(a[j], a[j + gap]);
                }
            }
        }
    }
    
    int main()
    {
        int arr[] = {49, 38, 65, 97, 76, 13, 27, 48, 55, 4};
        printf("Original array: ");
        int i;
        int len = sizeof(arr)/sizeof(int);
        for(i = 0; i < len; i++)
        {
            printf("%d  ", arr[i]);
        }
        printf("
    ");
    
        shellsort(arr, len);
        printf("Sorted array: ");
        for(i = 0; i < len; i++)
        {
            printf("%d  ", arr[i]);
        }
        printf("
    ");
    
        return 0;
    }
    

    2 Java实现

    import java.util.Arrays;
    
    public class Sort {
        public static void shellSort(int[] a) {  
            int i, j, gap;
            int len = a.length;
    
            for (gap = len / 2; gap > 0; gap /= 2) {
                for (i = gap; i < len; i++) {
                    for (j = i - gap; j >= 0 && a[j] > a[j + gap]; j -= gap) {
                        // 交换两个数
                        a[j] ^= a[j + gap];
                        a[j + gap] ^= a[j];
                        a[j] ^= a[j + gap];         
                    }
                }
            }
        }  
    
        public static void main(String[] args) {
            int[] arr = {49, 38, 65, 97, 76, 13, 27, 48, 55, 4};
            System.out.println("Original array: " + Arrays.toString(arr));  
            shellSort(arr);
            System.out.println("Sorted array: " + Arrays.toString(arr));   
        }
    }
    

    3 运行结果

    Original array: [49, 38, 65, 97, 76, 13, 27, 48, 55, 4]
    Sorted array: [4, 13, 27, 38, 48, 49, 55, 65, 76, 97]
    

    三、简单选择排序

    (一)基本原理(由小到大)

    先将n个数中最小的数与a[0]对换,再将a[1]到a[n-1]中最小的数与a[1]对换

    每比较一轮,找出未经排序的数中最小的一个。共比较n-1轮。


     
    3-1.jpg

    (二)编程实现

    1 Java实现

    package com.z;
    
    import java.util.Arrays;
    
    public class Sort {
        public static void selectSort(int[] array) {
            int j, minIndex;
            for (int i = 0; i < array.length - 1; i++) {
                minIndex = i;
        
                for (j = i + 1; j < array.length; j++) {
                    if (array[j] < array[minIndex]) {
                        minIndex = j;
                    }
                }
                
                if(minIndex != i) {
                    array[minIndex] ^= array[i];
                    array[i] ^= array[minIndex];
                    array[minIndex] ^= array[i];
                }
            }
        }
    
        public static void main(String[] args) {
            int[] arr = {57, 68, 59, 52};
            System.out.println("Original array: " + Arrays.toString(arr));  
            selectSort(arr);
            System.out.println("Sorted array: " + Arrays.toString(arr));   
        }
    }
    
    

    运行结果:

    Original array: [57, 68, 59, 52]
    
    Sorted array: [52, 57, 59, 68]
    

    2 C语言实现

    #include<stdio.h>
    
    void select_sort(int a[],int n)
    {
        int i, j, min;
         for(i = 0; i < n - 1; i++)
        {
            min = i;
            
            for(j = i + 1; j < n; j++)
            {
                if(a[j] < a[min])
                {
                    min = j;
                }
            }
            
            if(min != i)
            {
                a[min] = a[min] ^ a[i];
                a[i] = a[min] ^ a[i];
                a[min] = a[min] ^ a[i];
            }
        }
    }
    
    int main()
    {
        int b[] = {5, 4, 3, 2, 1};
        int len = sizeof(b) / sizeof(int);
        select_sort(b, len);
        
        for(int i = 0; i < len; i++)
        {
            printf("%d ",b[i]);
        }
        printf("
    ");
        
        return 0;
    }
    
    

    运行结果:

    1 2 3 4 5
    

    3 C++函数模板实现

    #include <iostream>
    using namespace std;
    
    #ifndef ARRAY_BASED_SORTING_FUNCTIONS
    #define ARRAY_BASED_SORTING_FUNCTIONS
    
    template<class T>
    void Swap(T &x, T &y)
    {
        x = x ^ y;
        y = x ^ y;
        x = x ^ y;
    }
    
    template<class T>
    void SelectionSort(T a[], int n)
    {
        int i, j, min;
        for(i = 0; i < n-1; i++)
        {
            min = i;
            for(j = i + 1; j < n; j++)
            {
                if(a[j] < a[min])
                {
                    min = j;
                }
            }
            
            if(min != i)
            {
                Swap(a[i], a[min]);
            }
        }
    }
    
    #endif
    
    int main()
    {
        int a[] = {14, 12, 10, 8, 11, 4, 2};
        int len = sizeof(a) / sizeof(int);
        SelectionSort(a, len);
        for (int i = 0; i < len; i++)
        {
            cout << a[i] << ' ';
        }
        cout << endl;
        
        return 0;
    }
    
    

    运行结果:

    2 4 8 10 11 12 14
    

    四、堆排序

    (一)什么是堆

    堆实际上是一棵完全二叉树,其任何一非叶节点满足性质:
    Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者
    Key[i]>=Key[2i+1]&&key>=key[2i+2],
    即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。
    堆分为大顶堆和小顶堆,满足Key[i]>=Key[2i+1]&&key>=key[2i+2]称为大顶堆,满足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]称为小顶堆。由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。

    (二)堆排序思想

    利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。
    以大顶堆为例,其基本思想为:
    a)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;
    b)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];
    c)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……,Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

    (三)操作过程

    a)初始化堆:将R[1..n]构造为堆;
    b)将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。
    因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。

    (四)例子和代码

    针对两步操作过程,咱们以整形数组a[]={16,7,3,20,17,8}为例。
    a)第一步是构造初始堆:
    首先根据该数组元素构建一个完全二叉树,得到

     
    4-1.jpg

    然后需要构造初始堆,则从最后一个非叶节点开始调整,调整过程如下:


     
    4-2.jpg
     
    4-3.jpg
     
    4-4.jpg

    上图中因为16,7,17三个节点不满足堆的性质,因此需要重新调整如下图:
     
    4-5.jpg

    这样就得到了初始堆。
    上面的过程实际上就是每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换,交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整。
    整个过程的实现代码如下:

    #include <stdio.h>
    
    void HeapAdjust(int *a,int i,int size)  //调整堆
    {
        // 如果i是叶子 节点就不用进行调整
        if(i >= size/2)
        {
            return;
        }
        
        // i非叶子节点,开始调整
        int lchild = 2 * i + 1;     // i的左孩子节点序号
        int rchild = 2 * i + 2;     // i的右孩子节点序号
        int max = i;                // 临时变量
        if(lchild < size && a[lchild] > a[max])
        {
            max = lchild;
        }
        if(rchild < size && a[rchild] > a[max])
        {
            max = rchild;
        }
        if(max != i)
        {
            // 将a[i]与a[max]对换
            a[i]   = a[i] ^ a[max];
            a[max] = a[i] ^ a[max];
            a[i]   = a[i] ^ a[max];
            
            // 若调整之后以max为父节点的子树不是堆,则对该子树继续调整
            HeapAdjust(a, max, size);
        }
    }
    
    void BuildHeap(int *a,int size)
    {
        for(int i = size/2 - 1; i >= 0; i--)    //非叶节点最大序号值为size/2
        {
            HeapAdjust(a, i, size);
        }
    }
    
    int main(int argc, const char * argv[])
    {
        int a[] = {16, 7, 3, 20, 17, 8};
        int size = sizeof(a) / sizeof(int);
        BuildHeap(a, size);     // 建立堆
        
        printf("构造出初始堆");
        for(int i = 0; i < size; i++)
        {
            printf("%d ", a[i]);
        }
        
        return 0;
    }
    

    运行结果:

    构造出初始堆  20 17 8 7 16 3
    

    b)有了初始堆之后,就可以进行排序

     
    4-6.jpg

    此时3位于堆顶不满足堆的性质需要继续调整:

     
    4-7.jpg

    调整后,3、7、16这个子堆不满足堆的性质,继续调整:

     
    4-8.jpg

    这样经过第一轮调整后,得到了一个有序数组{20}和一个调整后的堆。下面继续调整:

     
    4-9.jpg
     
    4-10.jpg
     
    4-11.jpg

    这样经过第二轮调整后,得到一个有序数组{17,20}和一个调整后的堆。继续调整:

     
    4-12.jpg
     
    4-13.jpg

    这样经过第三轮调整后,得到一个有序数组{16,17,20}和一个调整后的堆。继续调整:

     
    4-14.jpg
     
    4-15.jpg

    这样经过第四轮调整后,得到一个有序数组{8,16,17,20}和一个调整后的堆。继续调整:

     
    4-16.jpg

    这样经过第五轮调整后,得到一个有序数组{7,8,16,17,20}和一个调整后的堆,这个堆只有一个元素,且一定是整个数组中的最小值,所以不用调整。
    由上述过程可知,总共需要调整5轮,即sizeof(数组)-1轮。

    下面给出实现的代码:

    #include <stdio.h>
    
    void HeapAdjust(int *a,int i,int size)  //调整堆
    {
        // 如果i是叶子 节点就不用进行调整
        if(i >= size/2)
        {
            return;
        }
        
        // i非叶子节点,开始调整
        int lchild = 2 * i + 1;     // i的左孩子节点序号
        int rchild = 2 * i + 2;     // i的右孩子节点序号
        int max = i;                // 临时变量
        if(lchild < size && a[lchild] > a[max])
        {
            max = lchild;
        }
        if(rchild < size && a[rchild] > a[max])
        {
            max = rchild;
        }
        if(max != i)
        {
            // 将a[i]与a[max]对换
            a[i]   = a[i] ^ a[max];
            a[max] = a[i] ^ a[max];
            a[i]   = a[i] ^ a[max];
            
            // 若调整之后以max为父节点的子树不是堆,则对该子树继续调整
            HeapAdjust(a, max, size);
        }
    }
    
    void BuildHeap(int *a,int size)
    {
        for(int i = size/2 - 1; i >= 0; i--)    //非叶节点最大序号值为size/2
        {
            HeapAdjust(a, i, size);
        }
    }
    
    void HeapSort(int *a,int size)    //堆排序
    {
        BuildHeap(a, size);
        for(int i = size - 1; i > 0; i--)
        {
            // 交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面
            a[i] = a[i] ^ a[0];
            a[0] = a[i] ^ a[0];
            a[i] = a[i] ^ a[0];
            HeapAdjust(a, 0, i);      //重新调整堆顶节点成为大顶堆
        }
    }
    
    int main(int argc, const char * argv[])
    {
        int a[] = {16, 7, 3, 20, 17, 8};
        int size = sizeof(a) / sizeof(int);
        HeapSort(a, size);     // 堆排序
        
        printf("堆排序后的结果 ");
        for(int i = 0; i < size; i++)
        {
            printf("%d ", a[i]);
        }
        
        return 0;
    }
    

    运行结果:

    堆排序后的结果 3 7 8 16 17 20
    

    (五) 进一步分析

    从上述过程可知,堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1...n]中选择最大记录,需比较n-1次,然后从R[1...n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。

    五、冒泡排序

    (一)基本原理(由小到大):

    将相邻两个数比较,将大的调到后头。如果有n个数,则要进行n-1趟比较。

    在第1趟中要进行n-1次两两比较,在第j趟比较中要进行n-j次两两比较。

     
    5-1.png

    (二)代码实现

    1 C语言实现

    #include <stdio.h>
    
    void bubbleSort(int a[], int n)
    {
        for(int round = 0; round < n - 1; round++)
        {
            for(int i = 0; i < n - 1 - round; i++)
            {
                if(a[i] > a[i+1])
                {
                    a[i] = a[i] ^ a[i+1];
                    a[i+1] = a[i] ^ a[i+1];
                    a[i] = a[i] ^ a[i+1];
                }
            }
        }
    }
    
    int main()
    {
        int a[5] = {5, 3, 4, 1, 2};
        bubbleSort(a, 5);
        for (int i = 0; i < 5; i++)
        {
            printf("%d ", a[i]);
        }
        return 0;
    }
    

    运行结果:

    1 2 3 4 5 
    

    2 C++函数模板实现

    #include <iostream>
    using namespace std;
    
    #ifndef ARRAY_BASED_SORTING_FUNCTIONS
    #define ARRAY_BASED_SORTING_FUNCTIONS
    
    template<class T>
    void Swap(T &x,T &y)
    {
        T temp;
        temp=x;
        x=y;
        y=temp;
    }
    
    template<class T> void BubbleSort(T A[],int n)
    {
        int i,j;
        int lastExchangeIndex;
        i=n-1;
        while(i>0)
        {
            lastExchangeIndex=0;
            for(j=0;j<i;j++)
            {
                if(A[j+1]<A[j])
                {
                    Swap(A[j],A[j+1]);
                    lastExchangeIndex=j;
                }
            }
            i=lastExchangeIndex;
        }
    }
    #endif
    
    int main()
    {
        int a[] = {57, 68, 59, 52};
        int len = sizeof(a) / sizeof(int);
        BubbleSort(a, len);
        for (int i = 0; i < len; i++)
        {
            cout << a[i] << ' ';
        }
        cout << endl;
        
        return 0;
    }
    

    运行结果:

    52 57 59 68
    

    六、快速排序

    (一)基本思想

    选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。

    (二)例子

     
    6-1.png

    以{5, 9, 2, 7 ,8, 3, 6, 1, 4, 0}为例。
    选择第0个元素5作为参照数,咱们第一步的目标是把比5小的数都调整到5的左边,比5大的数都调到5的右边。
    (1)从左往右开始观察,发现9比5大,准备调整。再从右往左观察,发现0比5小,准备调整。对调9和0的位置。
    (2)继续从左往右观察,2比5小,不用调。继续往右,7比5大,准备调整。继续从右往左观赛,4比5小,准备调整。对调7和4的位置。
    (3)继续从左往右观察,8比5大,准备调整。继续从右往左观察,1比5小,准备调整。对调8和1的位置。
    (4)继续从左往右观察,3比5小,不用调整。继续往右观察,碰到6,准备调整。继续从右往左观察,第一个碰到的就是6,这时从左往右或者从右往左碰到的都是6,所以6不用调,也不需要再继续观察下去了。
    (5)最后一次调整一下3和5的位置。得到了第一步的目标,比5小的{3, 0, 2, 4, 1}都在5的左边,比5大的{6, 8, 7, 9}都在5的右边。
    (6)对新数列{3, 0, 2, 4, 1}和{6, 8, 7, 9}分别用上面的方法继续调整,直到所有的数都排完序为止。

    (三)C++代码实现

    #include <iostream>
    using namespace std;
    
    void QuickSort(int a[], int low, int high)
    {
        if(low >= high)
        {
            return;
        }
        
        int pivot = a[low];
        int i = low + 1;
        int j = high;
        
        while(i <= j)
        {
            if(a[i] <= pivot)
            {
                i++;
            }
            else if(a[j] > pivot)
            {
                j--;
            }
            else
            {
                // swap a[i], a[j]
                a[i] = a[i] ^ a[j];
                a[j] = a[i] ^ a[j];
                a[i] = a[i] ^ a[j];
                i++;
                j--;
            }
        }
        
        // swap a[low] , a[j]
        a[low] = a[j];
        a[j] = pivot;
        j--;
        
        QuickSort(a, low, j);
        QuickSort(a, i, high);
    }
    
    void PrintArrary(int data[], int size)
    {
        for (int i = 0; i < size; ++i)
        {
            cout << data[i] << " ";
        }
        
        cout << endl;
    }
    
    int main(int argc, const char** argv)
    {
        int array[]= {5, 9, 2, 7, 8, 3, 6, 1, 4, 0};
        int size = sizeof(array)/sizeof(int);
        QuickSort(array, 0, size - 1);
        PrintArrary(array, size);
        
        return 0;
    }
    

    运行结果:

    0 1 2 3 4 5 6 7 8 9
    

    七、归并排序

    (一)基本思想

    归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

     
    7-1.jpg

    (二)代码实现

    import java.util.Arrays;
    
    public class Sort {
    
        public static void mergeSort(int[] array) {
            sort(array, 0, array.length - 1);
        }
    
        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[] tmpArr = new int[data.length];
            int centerNext = center + 1;
            // 记录临时数组的索引
            int tmp = left;
            int index = left;
            while (left <= center && centerNext <= right) {
                //从两个数组中取出最小的放入临时数组
                if (data[left] <= data[centerNext]) {
                    tmpArr[tmp++] = data[left++];
                } else {
                    tmpArr[tmp++] = data[centerNext++];
                }
            }
    
            // 若右边数组还有剩余元素,把这些剩余元素依次放入临时数组
            while (centerNext <= right) {
                tmpArr[tmp++] = data[centerNext++];
            }
    
            // 若左边数组还有剩余元素,把这些剩余元素依次放入临时数组
            while (left <= center) {
                tmpArr[tmp++] = data[left++];
            }
    
            // 将临时数组中的内容复制回原数组
            while (index <= right) {
                data[index] = tmpArr[index++]; 
            }
        }
    
        public static void main(String[] args) {
            int[] arr = {57, 68, 59, 52, 72, 28, 96, 33};
            System.out.println("Original array: " + Arrays.toString(arr));  
            mergeSort(arr);
            System.out.println("Sorted array: " + Arrays.toString(arr));   
        }
    }
    

    运行结果:

    > javac -encoding utf-8 Sort.java
    > java Sort
    
    Original array: [57, 68, 59, 52, 72, 28, 96, 33]
    Sorted array: [28, 33, 52, 57, 59, 68, 72, 96]
    

    八、基数排序

    (一)基本思想

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

     
    8.png

    (二)代码实现

    package com.z;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    
    public class Sort {
    
        public static void radixSort(int[] array) {
            //获取最大的数
            int max = array[0];
            for (int i = 1; i < array.length; i++) {
                if (array[i] > max) {
                    max = array[i];
                }
            }
            
            int digitCount = 0;
            //判断位数,位数即排序的趟数
            while (max > 0) {
                max /= 10;
                digitCount++;
            }
            
            //建立10个数组;
            ArrayList<ArrayList<Integer>> list = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                ArrayList<Integer> list1 = new ArrayList<>();
                list.add(list1);
            }
    
            //进行digitCount次分配和收集;
            for (int i = 0; i < digitCount; i++) {
                //分配数组元素;
                for (int num : array) {
                    //得到数字的第i+1位数;
                    int x = num % (int)Math.pow(10, i + 1) / (int)Math.pow(10, i);
                    ArrayList<Integer> list2 = list.get(x);
                    list2.add(num);
                    list.set(x, list2);
                }
                int index = 0;
                //重新排列数组中的元素
                for (int k = 0; k < 10; k++) {
                    while (list.get(k).size() > 0) {
                        ArrayList<Integer> list3 = list.get(k);
                        array[index] = list3.get(0);
                        list3.remove(0);
                        index++;
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            int[] arr = {135, 242, 192, 93, 345, 11, 24, 19};
            System.out.println("Original array: " + Arrays.toString(arr));  
            radixSort(arr);
            System.out.println("Sorted array: " + Arrays.toString(arr));   
        }
    }
    

    运行结果:

    Original array: [135, 242, 192, 93, 345, 11, 24, 19]
    Sorted array: [11, 19, 24, 93, 135, 192, 242, 345]


  • 相关阅读:
    bzoj 1054
    bzoj 1047
    bzoj 2761
    bzoj 1191
    bzoj 2748
    bzoj_1003 物流运输
    新的开始( [USACO08OCT]打井Watering Hole)
    map
    Generic Cow Protests-G——60分做法
    逆序对
  • 原文地址:https://www.cnblogs.com/alan-blog-TsingHua/p/9607576.html
Copyright © 2020-2023  润新知