目录
冒泡排序(bubbleSort)
直接插入排序(insertSort)
希尔排序(shellSort)
选择排序(selectSort)
堆排序(heapSort)
快速排序(quickSort)
归并排序(mergeSort)
原理:从前往后,相邻的两个元素进行比较,满足升序或降序;(这里以升序为例)一次冒泡的 过程,至少可以保证把一个元素放到正确的位置;再升序的前提下,则每次冒泡是将大的元素往后移动。
下面为一次冒泡的过程
总共经历5次冒泡,即可将整个序列变为升序
代码如下:
public static void bubbleSort(long[] array){
for (int i = 0; i < array.length - 1; i++) {
boolean sorted=true;
for (int j = 0; j < array.length - i - 1; j++) {
if(array[j]>array[j+1]){
sorted=false;
swap(array,j,j+1);
}
}
if(sorted){
return;
}
}
}
原理:将一个序列 的第一个元素看做有序,将剩下的元素看做一个无序区间;然后从无须区间的第一个元素开始与有序区间的元素依次向前比较,若比前面的元素小,就将比他大的元素整体向后移动一位,再将该元素插入到正确的位置,则有序区间元素逐渐增加,无序区间元素逐渐减少,直到无序区间不存在任何元素,此时整个序列变为一个升序序列。
下面为直接插入排序的动画演示:
代码如下:
public static void insertSort(long[] array){
// 一共要取多少个元素来进行插入过程(无序区间里有多少个元素)
for (int i = 0; i < array.length - 1; i++) {
// 有序区间 [0, i] 至少在 i == 0 的时候得有一个元素
// 无序区间 [i + 1, n)
// 先取出无序区间的第一个元素,记为 k
long k = array[i + 1];
// 从后往前,遍历有序区间{
// 找到合适的位置退出
// 所谓合适的位置,就是第一次 k >= array[j] 的位置
int j;
for (j = i; j >= 0 && k < array[j]; j--) {
array[j + 1] = array[j]; // 将不符合条件的数据往后搬一格
}
array[j + 1] = k;
}
}
原理:希尔排序可以理解为插入排序的升级版(分组插入排序),也称为缩小增量排序。
将整个区间分为若干个区间(由相隔某个“增量”的元素组成),然后对这些子序列分别进行插入排序。完成后缩小增量的值,此此时又会形成若干个区间,重复之前的操作,直到增量为1时,对整个序列来一次插入排序,而此时的插入排序效率最高,因为整个序列已经有了基本的顺序,达到了一种有序的状态。
通常用gap表示增量,当一个序列有n个元素时,进行第一次排序时gap=n/2,之后gap=gap/2.
例:{4,2,6,9,1,3,5,8}
此时gap=8/2=4,可以分为四组{4,1}、{2,3}、{6,5}、{9,8}
进行组内排序形成一个新的序列{1,2,5,8,4,3,6,9},此时gap=gap/2=2,可以将序列分成两组
{1,5,4,6}、{2,8,3,9}
经过排序后又会形成一个新的序列{1,2,4,3,5,8,6,9},此时gap=2/2=1,则一整个序列为一组,但整个序列看上去已经显得部分有序,最后再进行一次插入排序,就能将整个序列变得有序。
得到的有序序列为:{1,2,3,4,5,6,8,9}。
代码如下:
public static void shellSort(int[] arr) {
int j, temp;
for (int gap = arr.length / 2; gap > 0; gap /= 2) { //每一次的增量
for (int i = gap; i < arr.length; i++) { //从下标为增量的元素开始向后遍历
temp = arr[i]; //提前保存 arr[i],否则会将其覆盖
for (j = i - gap; j >= 0; j -= gap) { //寻找插入位置
if (temp < arr[j]) { //满足条件进行下一步
arr[j + gap] = arr[j]; //将元素后移
} else {
break;
}
}
arr[j + gap] = temp; //插入 arr[i]
}
}
}
原理:首先将整个序列看做一个无序区间,将无序区间的第一个元素a0看做最小的,然后和后面的元素一次进行比较,若发现比a0还小的元素,就将这个元素看做最小的,继续和后面比较,若没有比该元素还小的元素,就将该元素和无序区间的第一个元素交换,此时有序区间便有了一个元素。然后重复上述操作,直到整个序列成为有序。
动画演示:
代码如下:
public static void selectSort(int[] array) {
for (int i = 0; i < array.length; i++) {
int minIndex = i;
for (int j = i; j < array.length; j++) {
if (array[j] < array[minIndex]) {
minIndex = j;
}
}
int temp = array[minIndex];
array[minIndex] = array[i];
array[i] = temp;
}
}
原理:基本原理也是选择排序,只不过不需用使用便利的方式来查找最大元素,而是通过堆的性质来找无序区间的最大数。如果升序排列就需要构建大堆,降序排列就要构建小堆。
基本步骤:
1.首先将无序区间构建成一个大根堆,这时整个堆顶元素就是最大的元素
2.将堆顶的元素与最后一个元素交换,此时最后一个元素就是最大的数,无序区间的元素会变成n-1个,再将无序区间进行向下调整
3.反复进行第2步,直到整个序列变得有序
举例:给定一个序列 {4,2,6,9,1,3,5,8}
第一步:构建大根堆,形成新的序列{9,8,5,6,1,3,4,2}
第二步,交换堆顶元素和最后一个元素,并对无序区间进行向下调整
第三步,重复第二步的操作
最后只有一个结点,排序完成,最终序列为{1,2,3,4,5,6,8,9}
注意:上述过程中每次交换完成后,最后一个元素并未在堆中呈现,但是在整个序列中是存在的,只不过由于是有序区间,所以不必在堆中呈现
代码如下:
public static void heapSort(long[] array) {
// 1. 建立大堆
createBigHeap(array);
// 2. 遍历 n - 1 次
for (int i = 0; i < array.length - 1; i++) {
// 2.1 交换之前的无序区间 [0, n - i)
swap(array, 0, array.length - i - 1);
// 交换之后的无序区间 [0, n - i - 1),元素个数 n - i - 1 个
// 2.1 对堆的 [0] 进行向下调整,堆里的元素个数就是无序区间的元素个数
shiftDown(array, array.length - i - 1, 0);
}
}
//进行向下调整
private static void shiftDown(long[] array, int size, int index) {
while (2 * index + 1 < size) {
int maxIdx = 2 * index + 1;
int right = maxIdx + 1;
if (right < size && array[right] > array[maxIdx]) {
maxIdx = right;
}
if (array[index] >= array[maxIdx]) {
return;
}
swap(array, index, maxIdx);
index = maxIdx;
}
}
//创建大根堆
private static void createBigHeap(long[] array) {
// 从最后一个元素的双亲开始
for (int i = (array.length - 2) / 2; i >= 0; i--) {
shiftDown(array, array.length, i);
}
}
原理:将一个序列中的第一个元素作为参照数,利用两个游标(左和右),左游标向右走,找大于基准值得;右游标向左走,找小于基准值的值,然后将序列中所有大于等于参照数的元素放在它后面,把所有小于参照数的元素放在它前面。通过一次快速排序,可将区间分为两部分,前面区间的元素小于后面区间的元素。此时再对两个区间分别进行快速排序,直到整个区间变得有序。
步骤:1.确定一个基准值(通常以序列的第一个元素为基准值)
2.利用两个游标进行比较,将小于基准值的元素放前面,将大于等于基准值的元素放后 面;一次快排结束以后,基准值处于中间位置,也就是整个序列的分界点
3.此时利用递归的思想,对两个区间进行快速排序
注意:这个基准值的选取和两个游标的位置不是固定的,是以区间划分的,每次快速排序需要在对应的区间选取相应的基准值和游标。
下面举例为大家讲解:
给定一个序列{4,2,6,9,1,3,5,8}
首先确定基准值为第一个元素 4,同时准备两个有游标 i 和 j,分别指向第一个元素和最后一个元素
然后先让j向左走,寻找比基准值 4 小的元素,所以j会在指向3的时候停下来,然后让i往右走,寻找大于 4 的元素,所以i会在指向6的时候停下来,然后交换i 和 j指向的元素,即交换 6 和 3
这时继续上述操作,让j先走,会在1处停下,然后i走,会在9处停下,交换两个元素
此时继续,但是 j 往左走一步就会与i重合,此时将j所指的1和基准值4进行交换,形成一个新序列 {1,2,3,4,9,6,5,8},这时就会发现以4位界,前面的都是小于4的,后面的都是大于4的。然后继续之前的操作,但此时是在两个区间分别进行操作,所以取的基准值是不相同的。利用递归的思想,在经历几次操作之后就会把一个无须的序列变成一个升序序列——{1,2,3,4,5,6,8,9}。
代码如下:
public static int[] quickSort(int[] arr, int low, int high) {
if (low > high) {
return null;
}
int i = low;
int j = high;
int temp = arr[low]; //temp就是参照数
while (i < j) {
while (temp <= arr[j] && i < j) { //先走指针 j ,从后往前移动
j--;
}
while (temp >= arr[i] && i < j) { //再走指针 i ,从前往后移动
i++;
}
if (i < j) { //交换两个符合要求的元素
int t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//将指针重合位置的元素与参照数进行交换
arr[low] = arr[i];
arr[i] = temp;
quickSort(arr, low, i - 1); //递归左边的序列
quickSort(arr, i + 1, high); //递归右边的序列
return arr;
}
原理:归并排序的核心思想是分治。将整个区间划分成与若干个小区间,对每个区间进行排序,然后进行合并(合并区间的原理是合并有序数组)。若一个区间有6个元素,那么最终会划分成6个区间,每个区间都是一个单独的元素,而每个区间也都成了有序区间。之后进行合并,合并后成为了3个区间,每两个元素为一个区间,对3个区间进行排序,形成了三个有序的区间。重复上述操作,直到合并成为一个区间,最后在进行一次排序,即可将整个序列变为一个有序的序列。
下面举一个例子进行详解:
给出一个序列{4,2,6,9,1,3,5,8},下面为整个过程
代码如下:
public static void mergeSort(long[] array){
mergeSortRange(array,0,array.length);
}
//处理待合并的区间
public static void mergeSortRange(long[] array,int from,int to){
int size=to-from;
if(size==1){
return;
}
int mid=from+(size/2);
mergeSortRange(array,from,mid);
mergeSortRange(array,mid,to);
merge(array,from,mid,to);
}
//合并有序数组
public static void merge(long[] array,int from,int mid,int to){
int size=to-from;
long[] other=new long[size];
int left=from; //左边小区间的下标
int right=mid; //右边小区间的下标
int dest=0; //临时空间的下标
while(left