本节内容是转载https://blog.csdn.net/yuxin6866/article/details/52771739的,并对部分进行了修改。
public class sort {
public static void main(String[] args) {
sort s = new sort();
int arr[] = { 6,1,2,7,9,3,4,5,10,8 };
s.bubbleSort(arr);
s.selectSort(arr);
s.insertSort(arr);
s.quickSort(arr, 0, arr.length - 1);
for (Object o : arr) {
System.out.print(o + " ");
}
}
1.冒泡排序
顾名思义就是整个过程像气泡一样往上升(从小到大),从第一个数开始一次对相邻的两个数进行比较,若前面的数大于后者,则交换位置,一轮比较结束后最大的数位于最后,再进行下一轮比较,...... 注意 比较时,不再于最后一个数进行比较。 外层是循环次数,内层是比较次数。
private void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
2.选择排序
以选择小的数为例, 第一轮找出最小的数,和第一个数进行交换;接着从第二个数开始找出次小的数,和第二个数交换,......
注意利用minIndex记录最小数的下标.
private void selectSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i; j < arr.length - 1; j++) {
if (arr[j] > arr[j + 1]) {
minIndex = j + 1;
}
}
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
3.插入排序
初始假设第一个数是有序的,第二个数开始和第一个数进行比较,若小于则插入到第一个数的前面(实则是以交换方式);第三数和前两个数进行比较,先和第二个数比,再和第一个数比;......
private void insertSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int temp = arr[i];
int j;
for (j = i; j > 0 && arr[j - 1] > temp; j--) {
arr[j] = arr[j - 1];
}
arr[j] = temp; // arr[j]是比for()中的arr[j]小一个数的,因为j--
}
}
4.快速排序
方法其实很简单:分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即j=10),指向数字8。
首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为什么)。哨兵j一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。
现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下。
6 1 2 5 9 3 4 7 10 8
到此,第一次交换结束。接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下。
6 1 2 5 4 3 9 7 10 8
第二次交换结束,“探测”继续。哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下。
3 1 2 5 4 6 9 7 10 8
到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。
private void quickSort(int[] arr, int left, int right) {
if (left < right) {
int temp = arr[left];
int i=left;
int j=right;
while (i < j) {
while (i < j && temp <= arr[j]) {
j--;
}
arr[i] = arr[j];
while (i < j && temp >= arr[i]) {
i++;
}
arr[j] = arr[i]; //注意这里的arr[i]和上面的arr[i]的i不一样,不是一个值
}
arr[i]=temp;
quickSort(arr,left,j-1);
quickSort(arr,i+1,right);
}
}
5.Shell排序
希尔排序是一种插入排序算法,又称作缩小增量排序。是对直接插入排序算法的改进。其基本思想是:
先取一个小于n的整数作为第一个增量,把全部数据分成个组。所有距离为的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量重复上述的分组和排序,直至所取的增量,即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法。
public static void shellSort(int[] array) {
int n = array.length;
int h;
for (h = n / 2; h > 0; h /= 2) {
for (int i = h; i < n; i++) {
for (int j = i - h; j >= 0; j -= h) {
if (array[j] > array[j + h]) {
int temp = array[j];
array[j] = array[j + h];
array[j + h] = temp;
}
}
}
}
}
6. 堆排序
6.1二叉堆的定义
1)父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2)每个结点的左子树和右子树都是一个二叉堆。
当父结点的键值总是大于或等于任何一个子节点的键值时为大根堆。当父结点的键值总是小于或等于任何一个子节点的键值时为小根堆。下面展示一个小根堆:
由于其它几种堆(二项式堆,斐波纳契堆等)用的较少,一般将二叉堆就简称为堆。
一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。
每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列,然后将这个新数据插入到这个有序数据中。
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i],小根堆则相反。
堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
(1)用大根堆排序的基本思想
① 先将初始数组建成一个大根堆,此堆为初始的无序区
② 再将最大的元素(即堆顶)和无序区的最后一个记录交换,由此得到新的无序区和有序区,且满足的值<=的值。
③由于交换后新的根可能违反堆性质,故应将当前无序区调整为堆。然后再次将中最大的元素和该区间的最后一个记录交换,由此得到新的无序区和有序区,且仍满足关系的值<=的值,同样要将调整为堆。
……
直到无序区只有一个元素为止。
(2)大根堆排序算法的基本操作:
①建堆,建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程,从len/2到0处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(hlen/2) 其中h表示节点的深度,len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。
②调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,因为是沿着深度方向进行调整的。
③堆排序:堆排序是利用上面的两个过程来进行的。首先是根据元素构建堆。然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有节点都取出。
public static void heapSort(int[] array) {
// 1. 创建最大堆:从最后一个节点的父节点开始
int lastIndex = array.length - 1;
int startIndex = (lastIndex - 1) / 2;
for (int i = startIndex; i >= 0; i--) {
maxHeap(sort, sort.length, i);
}
// 2. 排序:末尾与头交换,逐一找出最大值,最终形成一个递增的有序序列
for (int i = array.length - 1; i > 0; i--) {
int temp = array[0];
array[0] = array[i];
array[i] = temp;
maxHeap(array, i, 0);
}
}
private static void maxHeap(int[] data, int heapSize, int index) {
// 左子节点
int leftChild = 2 * index + 1;
// 右子节点
int rightChild = 2 * index + 2;
// 最大元素下标
int largestIndex = index;
// 分别比较当前节点和左右子节点,找出最大值
if (leftChild < heapSize && data[leftChild] > data[largestIndex]) {
largestIndex = leftChild;
}
if (rightChild < heapSize && data[rightChild] > data[largestIndex]) {
largestIndex = rightChild;
}
// 如果最大值是子节点,则进行交换
if (largestIndex != index) {
int temp = data[index];
data[index] = data[largestIndex];
data[largestIndex] = temp;
// 交换后,其子节点可能就不是最大堆了,需要对交换的子节点重新调整
maxHeap(data, heapSize, largestIndex);
}
}
7.归并排序
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并过程为:
比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。
归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。
归并操作的工作原理如下:
S1: 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
S2: 设定两个指针,最初位置分别为两个已经排序序列的起始位置
S3: 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
S4: 重复S3,直到某一指针超出序列尾
S5: 将另一序列剩下的所有元素直接复制到合并序列尾
public static void mergeSort(int[] array, int low, int high) {
int middle = (low + high) / 2;
if (low < high) {
mergeSort(array, low, middle);
mergeSort(array, middle + 1, high);
merge(array, low, middle, high);
}
}
public static void merge(int[] array, int low, int middle, int high) {
int[] temp = new int[high - low + 1];
int i = low;
int j = middle + 1;
int k = 0;
while (i <= middle && j <= high) {
if (array[i] < array[j]) {
temp[k++] = array[i++];
} else {
temp[k++] = array[j++];
}
}
while (i <= middle) {
temp[k++] = array[i++];
}
while (j <= high) {
temp[k++] = array[j++];
}
for (int m = 0; m < temp.length; m++) {
array[m + low] = temp[m];
}
}
8.基数排序
基数排序的原理如下:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
基数排序的方式有以下两种:
最高位优先(Most Significant Digit first)法,简称MSD法:先按排序分组,同一组中记录,关键码相等,再对各组按排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码对各子组排序后。再将各组连接起来,便得到一个有序序列。
最低位优先(Least Significant Digit first)法,简称LSD法:先从开始排序,再对进行排序,依次重复,直到对排序后便得到一个有序序列。
/**
* 基数排序(LSD)
*
* @param array 待排序数组
* @param d 表示最大的元素的位数
*/
public static void radixSort(int[] array, int d) {
int n = 1;
int times = 1; // 排序次数,由位数最多的元素决定
int[][] temp = new int[10][array.length]; //数组的第一维表示可能的余数0-9
int[] order = new int[10]; //数组order用来表示该位是i的元素个数
while (times <= d) {
for (int i = 0; i < array.length; i++) {
int lsd = ((array[i] / n) % 10);
temp[lsd][order[lsd]] = array[i];
order[lsd]++;
}
int k = 0;
for (int i = 0; i < 10; i++) {
if (order[i] != 0) {
for (int j = 0; j < order[i]; j++) {
array[k] = temp[i][j];
k++;
}
order[i] = 0;
}
}
n *= 10;
times++;
}
}