[Classic] algorithm N003: Sort upgraded version of The Three Musketeers (Heap, Shell, Shaker)

3.1 Heap Sort (selection sort upgraded version)

Select the sort of conceptual simplicity, never ordering part of every election a minimum, insert the rear end of the sorted part of that time is mainly spent on the entire unsorted part to find the minimum, if we make the search for minimum speed up the way to select sorting method also can speed up the rate, Heap sort of let the search path from the root to the last leaf, rather than the entire unsorted parts, so it is called a modified selection sort.

3.1.1 resolution process

Heap sort using Heap Tree (accumulation of tree), a tree is a data structure, and the accumulation of tree is a binary tree, that is, every parent at most two child nodes (detailed definition of the tree data structure also see books ), a bulk of less than if the parent node tree child node, is called bulk minimum (Min Heap), if the parent node is greater than the child nodes, it is called the maximum stack (Max Heap), while the same level of child nodes disregard its magnitude relation, for example, the following is a stacked tree:
Here Insert Picture Description
can be a one-dimensional array to store all the elements of the tree and its stacking order, for convenience of calculation, the starting index is used instead of a 0, 1 is a root index positions, if left after the child node stored in the array index s, the parent node of the index s / 2, and the right child node s + 1, as shown above to convert the tree of FIG stacking one-dimensional array as follows like this:
Here Insert Picture Description
first you must know how to build a tree accumulation, the accumulation of elements added to the tree will be placed in the first position of the last leaf node, and then check whether the parent node is less than the child nodes (minimum accumulation), small Continuously switching element and the parent node until the condition is met accumulation trees, such as adding a accumulated on the element in FIG. 12, the tree is deposited adjustment method is as follows:
Here Insert Picture Description
after stacking the tree is set up, the roots must be the smallest of all the elements values, your purpose is to:

  1. The minimum removed
  2. Then adjust the tree to tree accumulation

Repeating the above steps, can achieve the effect of the sort, the minimum value of the root extraction mode is switched to the last leaf node, leaf nodes and then cut, stacked readjusted tree tree, as follows:
Here Insert Picture Description
after adjustment is completed, the root node is the minimum, so we can repeat this step, then remove the minimum value, and adjust the tree accumulation tree, as follows:
Here Insert Picture Description

如此重覆步骤之后,由于使用一维阵列来储存堆积树,每一次将树叶与树根交换的动作就是将最小值放至后端的阵列,所以最后阵列就是变为已排序的状态。

其实堆积在调整的过程中,就是一个选择的行为,每次将最小值选至树根,而选择的路径并不是所有的元素,而是由树根至树叶的路径,因而可以加快选择的过程, 所以Heap排序法才会被称之为改良的选择排序法。

3.1.2 代码执行

package com.gavinbj.leetcode.classic;

import java.util.Arrays;

public class SortUtils {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		int[] number = new int[10];
		for (int i = 0; i < 10; i++) {
			number[i] = (int) (Math.random() * 100);
		}

		heapSort(number);
	}


	public static void heapSort(int[] number) {
		
		int[] tmp = new int[number.length + 1];

		// 构建一个存储数组
		for (int i = 1; i < tmp.length; i++) {
			tmp[i] = number[i - 1];
		}
		createHeap(tmp);
		System.out.println("初始化堆数组:" + Arrays.toString(number));

		int m = number.length;
		while (m > 1) {
			swap(tmp, 1, m);
			m--;

			int p = 1;
			int s = 2 * p;

			while (s <= m) {
				if (s < m && tmp[s + 1] < tmp[s]) {
					s++;
				}
				if (tmp[p] <= tmp[s]) {
					break;
				}
				swap(tmp, p, s);
				p = s;
				s = 2 * p;
			}
		}

		// 这边将排序好的临时数组设定回原数组
		for (int i = 0; i < number.length; i++) {
			number[i] = tmp[i + 1];
		}
		
		System.out.println("高级选择排序(HeapSort)结果:" + Arrays.toString(number));
	}

	/**
	 * A:初始化堆数组
	 * 
	 * @param tmp
	 */
	private static void createHeap(int[] tmp) {
		int[] heap = new int[tmp.length];

		for (int i = 0; i < heap.length; i++) {
			heap[i] = -1;
		}

		for (int i = 1; i < heap.length; i++) {
			heap[i] = tmp[i];
			int s = i;
			int p = i / 2;
			while (s >= 2 && heap[p] > heap[s]) {
				swap(heap, p, s);
				s = p;
				p = s / 2;
			}
		}

		for (int i = 1; i < tmp.length; i++) {
			tmp[i] = heap[i];
		}

	}

	private static void swap(int[] number, int i, int j) {
		int t;
		t = number[i];
		number[i] = number[j];
		number[j] = t;
	}

}

3.2 Shell排序(插入排序升级版)

插入排序法由未排序的后半部前端取出一个值,插入已排序前半部的适当位置,概念简单但速度不快。排序要加快的基本原则之一,是让后一次的排序进行时,尽量利用前一次排序后的结果,以加快排序的速度,Shell排序法即是基于此一概念来改良插入排序法。

3.2.1 解析过程

Shell排序法最初是D.L Shell于1959所提出,假设要排序的元素有n个,则每次进行插入排序时并不是所有的元素同时进行时,而是取一段间隔。Shell首先将间隔设定为n/2,然后跳跃进行插入排序,再来将间隔n/4,跳跃进行排序动作,再来间隔设定为n/8、n/16,直到间隔为1之后的最 后一次排序终止,由于上一次的排序动作都会将固定间隔内的元素排序好,所以当间隔越来越小时,某些元素位于正确位置的机率越高,因此最后几次的排序动作将 可以大幅减低。

举个例子来说,假设有一未排序的数字如右:89 12 65 97 61 81 27 2 61 98

数字的总数共有10个,所以第一次我们将间隔设定为10 / 2 = 5,此时我们对间隔为5的数字进行排序,如下所示:
Here Insert Picture Description
画线连结的部份表示 要一起进行排序的部份,再来将间隔设定为5 / 2的商,也就是2,则第二次的插入排序对象如下所示:
Here Insert Picture Description

再来间隔设定为2 / 2 = 1,此时就是单纯的插入排序了,由于大部份的元素都已大致排序过了,所以最后一次的插入排序几乎没作什么排序动作了:
Here Insert Picture Description
将间隔设定为n/2是D.L Shell最初所提出,在教科书中使用这个间隔比较好说明,然而Shell排序法的关键在于间隔的选定,不同的间隔选定法可以将Shell排序法的速度再加快。

3.2.2 代码执行

package com.gavinbj.leetcode.classic;

import java.util.Arrays;

public class SortUtils {

	public static void main(String[] args) {

		int[] number = new int[10];
		for (int i = 0; i < 10; i++) {
			number[i] = (int) (Math.random() * 100);
		}

		shellSort(number);
		System.out.println("ShellSort结果:" + Arrays.toString(number));
	}

	/**
	 * Shell排序,改进版插入排序
	 * @param number
	 */
	public static void shellSort(int[] number) {

		int gap = number.length / 2;
		while (gap > 0) {
			for (int k = 0; k < gap; k++) {
				for (int i = k + gap; i < number.length; i += gap) {
					for (int j = i - gap; j >= k; j -= gap) {
						if (number[j] > number[j + gap]) {
							swap(number, j, j + gap);
						} else
							break;
					}
				}
			}

			gap /= 2;
		}
	}
	

	private static void swap(int[] number, int i, int j) {
		int t;
		t = number[i];
		number[i] = number[j];
		number[j] = t;
	}

}

3.3 Shaker排序(冒泡排序升级版)

Shaker排序又叫鸡尾酒排序或双向冒泡排序,它是冒泡排序的一种轻微改进。与冒泡排序相同,Shaker排序也是一种稳定排序算法。不同的是,普通的冒泡排序算法仅是从低到高比较序列里的每个元素,或者说普通的冒泡排序算法只能每次从前向后按一个次序进行遍历,而Shaker排序方法每次遍历却包括两个方向,先从前向后再从后向前,在从前往后遍历后能记录最后发生交换的两个元素位置,从后往前遍历时就从这个位置开始。这种双向交替比较不仅可以使小的浮上水面,同时也会使大的沉倒水底,因而较普通的冒泡算法在效率上有所改进。

3.3.1 解析过程

在Shaker排序法中,具有n个待排序元素的数据表将分n/2个阶段来完成排序操作。每个阶段的Shaker排序都包括一个从左向右的遍历和一个从右向左的遍历。在遍历过程中会将两个相邻元素进行比较,如果它们的顺序是非正常的就将它们做交换。

传统的冒泡排序的交换的动作并不会一直进行至数组的最后一个,而是会进行至MAX-i-1,所以排序的过程中,数组右方排序好的元素会一直增加,使得左边排序的次数逐渐减少,方括号括住的部份表示已排序完毕。如例所示:

排序前:95 27 90 49 80 58 6 9 18 50

  • 27 90 49 80 58 6 9 18 50 [95] 95浮出
  • 27 49 80 58 6 9 18 50 [90 95] 90浮出
  • 27 49 58 6 9 18 50 [80 90 95] 80浮出
  • 27 49 6 9 18 50 [58 80 90 95] …
  • 27 6 9 18 49 [50 58 80 90 95] …
  • 6 9 18 27 [49 50 58 80 90 95] …
  • 6 9 18 [27 49 50 58 80 90 95]

Shaker排序也使用如上概念,如果让左边的元素也具有这样的性质,让左右两边的元素都能先排序完成,如此未排序的元素会集中在中间,由于左右两边同时排序,中间未排序的部份将会很快的减少。方法就在于气泡排序的双向进行,先让气泡排序由左向右进行,再来让气泡排序由右往左进行,如此完成一次排序的动作,而您必须使用left与right两个标识来记录左右两端已排序的元素位置。

Shaker排序的例子如下所示:

排序前:45 19 77 81 13 28 18 19 77 11

  • 往右排序:19 45 77 13 28 18 19 77 11 [81]
    向左排序:[11] 19 45 77 13 28 18 19 77 [81]
  • 往右排序:[11] 19 45 13 28 18 19 [77 77 81]
    向左排序:[11 13] 19 45 18 28 19 [77 77 81]
  • 往右排序:[11 13] 19 18 28 19 [45 77 77 81]
    向左排序:[11 13 18] 19 19 28 [45 77 77 81]
  • 往右排序:[11 13 18] 19 19 [28 45 77 77 81]
    向左排序:[11 13 18 19 19] [28 45 77 77 81]

如上所示,括号中表示左右两边已排序完成的部份,当left > right时,则排序完成。

3.3.2 代码执行

package com.gavinbj.leetcode.classic;

import java.util.Arrays;

public class SortUtils {

	public static void main(String[] args) {

		int[] number = new int[10];
		for (int i = 0; i < 10; i++) {
			number[i] = (int) (Math.random() * 100);
		}

		shakerSort(number);
		System.out.println("ShakerSort结果:" + Arrays.toString(number));
	}

	/**
	 * Shaker排序(改良版冒泡排序)
	 * @param number
	 */
	public static void shakerSort(int[] number) {
		
		int i, left = 0, right = number.length - 1, shift = 0;

		while (left < right) {
			// 向右进行气泡排序
			for (i = left; i < right; i++) {
				if (number[i] > number[i + 1]) {
					swap(number, i, i + 1);
					shift = i;
				}
			}
			right = shift;

			// 向左进行气泡排序
			for (i = right; i > left; i--) {
				if (number[i] < number[i - 1]) {
					swap(number, i, i - 1);
					shift = i;
				}
			}
			left = shift;
		}
	}
	

	private static void swap(int[] number, int i, int j) {
		int t;
		t = number[i];
		number[i] = number[j];
		number[j] = t;
	}

}
Published 37 original articles · won praise 24 · views 90000 +

Guess you like

Origin blog.csdn.net/gavinbj/article/details/104329781