您的位置:首页 > 理论基础 > 数据结构算法

归并排序、堆排序、快速排序

2017-04-04 20:17 417 查看
归并排序
递归法

迭代法

堆排序

快速排序

归并排序

最好情况:Ο(nlogn)

最坏情况:Ο(nlogn)

平均情况:Ο(nlogn)

辅助空间:Ο(n)

稳定性:稳定

归并排序是创建在归并操作上的一种有效的排序算法,效率为O(nlogn),1945年由冯·诺伊曼首次提出。

归并排序的实现分为递归实现与非递归(迭代)实现。递归实现的归并排序是算法设计中分治策略的典型应用,我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。

递归法

过程:

计算左右边界和中间分割线

递归左右数组

将左右数组合并

3.1. 提取左右数组为左右临时数组

3.2. 在左右临时数组提取较大(小)的元素放入原数组

public static void main(String[] args) {
int[] arr = new int[]{5, 8, 6, 12, 2, 4, 10, 9, 1, 7, 3, 13, 0};
int length1 = arr.length;
leftArr = new int[length];
rightArr = new int[length];
mergeSortRecursion(arr, 0, length - 1);
}

static void mergeSortRecursion(int[] arr, int left, int right) {    //递归实现的归并排序
int middle = (left + right) / 2;    //取中间位置
if (left < right) {
mergeSortRecursion(arr, left, middle);    //递归左边数组
mergeSortRecursion(arr, middle + 1, right);    //递归右边数组
merge(arr, left, middle, right);    //将已经排序的左边数组和右边数组进行排序
print(arr);
}
}

static void merge(int[] arr, int left, int middle, int right) {
int[] leftArr = new int[right - left + 1];
int[] rightArr = new int[right - left + 1];
System.arraycopy(arr, left, leftArr, 0, middle - left + 1);    //将左边数组拷贝到leftArr
System.arraycopy(arr, middle + 1, rightArr, 0, right - middle);    //将右边数组拷贝到rightArr
leftArr[middle - left + 1] = Integer.MAX_VALUE;    //在数组最后面设置正无穷,使下面循环内的判断不会越界
rightArr[right - middle] = Integer.MAX_VALUE;
for (int k = left, i = 0, j = 0; k <= right; k++) {
if (leftArr[i] <= rightArr[j]) {
arr[k] = leftArr[i++];
} else {
arr[k] = rightArr[j++];
}
}
}


输出:

5 8 6 12 2 4 10 9 1 7 3 13 0

5 8 6 12 2 4 10 9 1 7 3 13 0

5 6 8 12 2 4 10 9 1 7 3 13 0

5 6 8 12 2 4 10 9 1 7 3 13 0

5 6 8 12 2 4 10 9 1 7 3 13 0

2 4 5 6 8 10 12 9 1 7 3 13 0

2 4 5 6 8 10 12 1 9 7 3 13 0

2 4 5 6 8 10 12 1 7 9 3 13 0

2 4 5 6 8 10 12 1 7 9 3 13 0

2 4 5 6 8 10 12 1 7 9 0 3 13

2 4 5 6 8 10 12 0 1 3 7 9 13

0 1 2 3 4 5 6 7 8 9 10 12 13

迭代法

过程:

计算步长,步长每次*2

根据步长计算左右边界和中间分割线

将左右数组合并

重新计算左右边界

static void mergeSortIteration(int[] arr, int left, int right) {    //迭代实现的归并排序
for (int gap = 1; gap <= right - left; gap *= 2) {
int leftPoint = left, rightPoint;    //left和right都是数组的左右边界,leftPoint和rightPoint都是代表数组位置的游离指针;
while (leftPoint + gap <= right) {
rightPoint = leftPoint + gap * 2 - 1;
int middle = (leftPoint + rightPoint) / 2;    //middle一定要放在检查rightPoint的前面,这样才能准确分块
if (rightPoint > right) {
rightPoint = right;
}
merge(arr, leftPoint, middle, rightPoint);
leftPoint = rightPoint + 1;
print(arr);
}
}
}


输出:

5 8 6 12 2 4 10 9 1 7 3 13 0

5 8 6 12 2 4 10 9 1 7 3 13 0

5 8 6 12 2 4 10 9 1 7 3 13 0

5 8 6 12 2 4 9 10 1 7 3 13 0

5 8 6 12 2 4 9 10 1 7 3 13 0

5 8 6 12 2 4 9 10 1 7 3 13 0

5 6 8 12 2 4 9 10 1 7 3 13 0

5 6 8 12 2 4 9 10 1 7 3 13 0

5 6 8 12 2 4 9 10 1 3 7 13 0

2 4 5 6 8 9 10 12 1 3 7 13 0

2 4 5 6 8 9 10 12 0 1 3 7 13

0 1 2 3 4 5 6 7 8 9 10 12 13





堆排序

最好情况:Ο(nlogn)

最坏情况:Ο(nlogn)

平均情况:Ο(nlogn)

辅助空间:Ο(1)

稳定性:不稳定

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构(通常堆是通过一维数组来实现的),并同时满足堆的性质:即子结点的键值总是小于(或者大于)它的父节点。

过程:

创建初始堆(最大堆或最小堆)

1.1. 调整每个非叶子节点及其子堆

根节点与最后元素交换

2.1. 逐渐缩小堆

2.2. 从堆顶进行堆调整

public static void main(String[] args) {
int[] arr = new int[]{5, 8, 6, 12, 2, 4, 10, 9, 1, 7, 3, 13, 0, 11};
sortHeap(arr);
}

static void sortHeap(int[] arr) {
buildHeap(arr);
for (int i = arr.length - 1; i >= 1; i--) {      //对最大堆进行排序
exchange(arr, 0, i);      //将第根节点元素与最后节点(i)元素交换
heapAdjust(arr, 0, i);    //最后节点元素被固定,即可交换的数组长度为i
print(arr);
}
}

static void buildHeap(int[] arr) {    //建堆(最大堆)
for (int i = (arr.length - 1) / 2; i >= 0; i--) {
heapAdjust(arr, i, arr.length);    //自下向上对每个非叶子节点进行调整
print(arr);
}
}

static void heapAdjust(int[] arr, int i, int heapSize) {
int leftChild = i * 2 + 1;    //i节点(父节点)的左孩子和右孩子
int rightChild = leftChild + 1;
int largest = i;    //父节点、左孩子、右孩子进行大小比较
if (leftChild < heapSize && arr[leftChild] > arr[i]) {
largest = leftChild;
}
if (rightChild < heapSize && arr[rightChild] > arr[leftChild]) {
largest = rightChild;
}
if (largest != i) {    //如果最大值不是父节点,那么不符合堆性质,调整最大值位置
exchange(arr, i, largest);
heapAdjust(arr, largest, heapSize);    //从被调整的孩子继续递归
}
}

static void exchange(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}

static void print(int[] arr) {
for (int a : arr) {  //输出
System.out.print(a + "\t");
}
System.out.println();
}


输出:

5 8 6 12 2 4 11 9 1 7 3 13 0 10

5 8 6 12 2 13 11 9 1 7 3 4 0 10

5 8 6 12 7 13 11 9 1 2 3 4 0 10

5 8 6 12 7 13 11 9 1 2 3 4 0 10

5 8 13 12 7 6 11 9 1 2 3 4 0 10

5 12 13 9 7 6 11 8 1 2 3 4 0 10

13 12 11 9 7 6 10 8 1 2 3 4 0 5

12 9 11 8 7 6 10 5 1 2 3 4 0 13

11 9 10 8 7 6 0 5 1 2 3 4 12 13

10 9 6 8 7 4 0 5 1 2 3 11 12 13

9 8 6 5 7 4 0 3 1 2 10 11 12 13

8 7 6 5 2 4 0 3 1 9 10 11 12 13

7 5 6 3 2 4 0 1 8 9 10 11 12 13

6 5 4 3 2 1 0 7 8 9 10 11 12 13

5 3 4 0 2 1 6 7 8 9 10 11 12 13

4 3 1 0 2 5 6 7 8 9 10 11 12 13

3 2 1 0 4 5 6 7 8 9 10 11 12 13

2 0 1 3 4 5 6 7 8 9 10 11 12 13

1 0 2 3 4 5 6 7 8 9 10 11 12 13

0 1 2 3 4 5 6 7 8 9 10 11 12 13



堆排序是不稳定的排序算法,不稳定发生在堆顶元素与A[i]交换的时刻。

快速排序

最好情况:Ο(nlogn)

最坏情况:Ο(n2)

平均情况:Ο(nlogn)

辅助空间:Ο(logn)~ Ο(n)

稳定性:不稳定

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个元素要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。

过程:

按已坐好位置的基准将数组分为左右两个子数组

将比基准小的元素放到前面,比基准大的元素放到后面

将基准放置到准确位置

public static void main(String[] args) {
int[] arr = new int[]{5, 8, 6, 12, 2, 4, 10, 9, 1, 7, 3, 13, 0, 11};
quickSort(arr, 0, arr.length - 1);
}

static void quickSort(int[] arr, int left, int right) {    //递归分配基准的左右数组
if (left < right) {
int index = partition(arr, left, right);
quickSort(arr, left, index - 1);
quickSort(arr, index + 1, right);
}
}

static int partition(int[] arr, int left, int right) {    //比基准小的放到基准左边,比基准大的放到基准右边
int pivot = arr[right];    //基准值,不一定是最右侧的元素
int lastArrTail = left;    //被排序数列最左侧的游离指针,指针所在位置及往前位置的元素,皆比基准小
for (int i = left; i < right; i++) {    //将比基准小的元素与比基准大的元素进行交换
if (arr[i] <= pivot) {
exchange(arr, i, lastArrTail++);
}
}
exchange(arr, right, lastArrTail);    //将基准放在游离指针的位置,该位置及往后的元素比基准大,该位置往前的元素比基准小
print(arr);
return lastArrTail;    //排布完成后,返回该基准的位置,该基准已经坐在正确的排列位置上了,但基准的前后序列可能还存在乱序
}


输出:

5 8 6 2 4 10 9 1 7 3 0 11 12 13

0 8 6 2 4 10 9 1 7 3 5 11 12 13

0 2 4 1 3 5 9 8 7 6 10 11 12 13

0 2 1 3 4 5 9 8 7 6 10 11 12 13

0 1 2 3 4 5 9 8 7 6 10 11 12 13

0 1 2 3 4 5 9 8 7 6 10 11 12 13

0 1 2 3 4 5 6 8 7 9 10 11 12 13

0 1 2 3 4 5 6 8 7 9 10 11 12 13

0 1 2 3 4 5 6 7 8 9 10 11 12 13

0 1 2 3 4 5 6 7 8 9 10 11 12 13



快速排序是不稳定的排序算法,不稳定发生在基准元素与A[tail+1]交换的时刻。

来源:http://www.cnblogs.com/eniac12/p/5329396.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐