小朋友学数据结构(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("\n");

    shellsort(arr, len);
    printf("Sorted array: ");
    for(i = 0; i < len; i++)
    {
        printf("%d  ", arr[i]);
    }
    printf("\n");

    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("\n");

    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]

原文地址:https://www.cnblogs.com/alan-blog-TsingHua/p/9607576.html

时间: 2024-11-06 09:25:51

小朋友学数据结构(4):归并排序的相关文章

小朋友学数据结构(5):顺序查找法

小朋友学数据结构(5):顺序查找法 查找是最常见的数据操作之一,也是数据结构的核心运算之一,其重要性不言而喻. 顺序查找是最简单的查找策略,对于小规模的数据,顺序查找是个不错的选择. (一)基本思想 从数据的第一个元素开始,依次比较,直到找到目标数据或查找失败. 1 从表中的第一个元素开始,依次与关键字比较. 2 若某个元素匹配关键字,则查找成功. 3 若查找到最后一个元素还未匹配关键字,则查找失败. 1.png (二)时间复杂度 顺序查找平均关键字匹配次数为表长的一半,其时间复杂度为O(n).

小朋友学数据结构(10):基数排序

小朋友学数据结构(10):基数排序 一.基本思想 将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零.然后,从最低位(即个位数)开始,依次进行一次排序.这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列. 与其他排序不同的是,基数排序不涉及数的交换. 基数排序是一种稳定的排序算法. 8.png 二.主要步骤 从上面的计算过程,咱们可以看出,基数排序主要有三个步骤: 1.把所有元素都分配到相应的桶中(因为整数每位数有0~9共十种可能,所以通常需要10个桶) 2.把

小朋友学数据结构(8):直接插入排序

小朋友学数据结构(8):直接插入排序 (一)基本思想 在要排序的一组数中,假设前面(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 =

小朋友学数据结构(7):快速排序

小朋友学数据结构(7):快速排序 一.快速排序 (一)基本思想 选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分. (二)例子 6-1.png 以{5, 9, 2, 7 ,8, 3, 6, 1, 4, 0}为例. 选择第0个元素5作为参照数,咱们第一步的目标是把比5小的数都调整到5的左边,比5大的数都调到5的右边. (1)从左往右开始观

小朋友学数据结构(9):希尔排序

小朋友学数据结构(9):希尔排序 (一)基本思想 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序:随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止. (二)例子 有一个数组,其原始数组为: 2-1.png 取初始增量gap = length / 2 = 5,这样就将整个数组分为5组(每组用相同的颜色表示) 2-2.png 将这5组的数据分别按由小到大的顺序排列,结果为 2-3.png 缩小增量gap = gap / 2 = 2,整

小朋友学数据结构(6):折半查找法

小朋友学数据结构(6):折半查找法 折半查找法又称为二分查找法. (一)基本思想 假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功:否则利用中间位置记录将表分成前.后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表. 重复以上过程,直到找到满足条件的记录,此时查找成功:或直到子表不存在为止,此时查找不成功. 2.png (二)时间复杂度 二分查找的基本思想是将n个元素分成大致相等的两部分,取a[n/2]与x

小朋友学数据结构(3):二叉树的建立和遍历

小朋友学数据结构(3):二叉树的建立和遍历 一.基本概念 BinaryTree.png 二叉树:每个结点的子结点个数不大于2的树,叫做二叉树. 根结点:最顶部的那个结点叫做根结点,根结点是所有子结点的共同祖先.比如上图中的"7"结点就是根结点. 子结点:除了根结点外的结点,都叫子结点. 叶子结点:没有子结点的结点,叫做叶子结点.比如上图中的"1"结点."5"结点和"11"结点. 二叉树的遍历,有三种: (1)前序遍历:先遍历根

小朋友学数据结构(11):堆排序

小朋友学数据结构(11):堆排序 (一)什么是堆 堆实际上是一棵完全二叉树,其任何一非叶节点满足性质: 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]称为大顶堆,满足

小朋友学数据结构(2):栈

小朋友学数据结构(2):栈 栈是一种先入后出的数据结构. 如下图所示,入栈的顺序为1.2.3:出栈的顺序则反过来:3.2.1. stack.png 可以想象往一个箱子里放书,先放进去的书必然在箱子的底部,最后放进去的书在箱子的顶部.拿书的时候则要先拿顶部(后放进去)的书,最先放进去的书最后才能拿出来. 栈可以用链表来实现: #include<iostream> using namespace std; struct node //定义栈的结点结构 { int data; node *next;