排序算法总结
插入排序
直接插入排序
- 思路:将待排序数组分为有序部分和无序部分(对于未排序数组来说,有序部分指的就是第一个元素,无序部分是剩下的元素),每次将无序部分的第一个元素插入到有序部分中的合适位置,有序部分长度加一,对应的无序部分长度减一,一直到无序部分长度为零。
- 图解:
如图,第一次用无序部分的第一个元素:4和前面的有序部分—[5]进行比较,并将4插入目标位置
第二次用无序部分的第一个元素:8和前面有序部分—[4 5]进行比较,并插入到合适位置。
重复上述过程即可完成排序 - 代码:
public static void insertSort(int[] arr) {
if (arr.length <= 1) {
return;
}
for (int index = 1; index < arr.length; index++) { // 将所有无序部分遍历一遍
int temp = arr[index];
int i;
for (i = index; i >= 1 && temp <= arr[i - 1]; i--) { // 这个循环用来寻找要插入的位置
}
// 这行代码的作用是:将要插入位置之后的元素依次向后移动,为要插入的元素空出位置
if (index - i >= 0) System.arraycopy(arr, i, arr, i + 1, index - i);
arr[i] = temp;
}
}
希尔排序
- 思路:它相当于复杂版的插入排序,他将待排序数组按照一定规则分为若干部分(每部分长度相同),对每部分中相同位置的元素使用直接插入排序,之后,再将这些部分分为更小的小块,对每个小块中相同位置的元素再次进行直接插入排序,一直分割,直至小块不可再分(也就是说,最后一次排序相当于对整个数组进行了一次直接插入排序)
- 图解:
如图,给出了一组待排序数,将其分为两份,然后对两份中对应位置上的数据进行直接插入排序,
图中已给出排序结果
将之前的排序结果再分为四部分,对每份中碎影位置对的元素进行直接插入排序,图中已给出本次排序结果
将上次的排序结果再分为更小的部分,对每部分中对应位置的元素使用直接插入排序,图中已给出本次排序结果
由于最后一次的结果不可再分为更小的小块,故排序结束
- 代码:
public static void shellSort(int[] arr) {
for (int step = arr.length / 2; step > 0; step /= 2) { // 除2就是将小块每次二等分
for (int start = 0; start < step; start++) { // 这个循环相当于移动每个小块中的碎影位置
innerShellSort(arr, arr.length, step, start);
}
}
}
// 这个方法就是根据给入的数据进行直接插入排序
private static void innerShellSort(int[] arr, int len, int step, int start) {
for (int index = start + step; index < len; index += step) {
int temp = arr[index];
int i;
for (i = index; i >= step && temp <= arr[i - step]; i -= step) {
}
for (int j = index; j > i; j -= step) {
arr[j] = arr[j - step];
}
arr[i] = temp;
}
}
选择排序
简单选择排序
- 思路:依次确定数组中最大或最小的数字(降序or升序)放到数组最前面
- 图解:
进行完第一次数据交换之后,第一个位置上的数就是最大数,如图中第一行。接着从剩下的数字中找取最大数,放在第二个位置上,如图中第二行。依次进行操作,直到数组排列完毕 - 代码:
public static void selectSort(int[] arr) {
int len = arr.length;
int minIndex = 0;
for (int index = 0; index < len; index++) {
minIndex = index;
for (int i = index; i < len; i++) { // 这个循环用来找到剩余数组中的最小值下标
if (arr[minIndex] > arr[i]) {
minIndex = i;
}
}
int temp = arr[index];
arr[index] = arr[minIndex];
arr[minIndex] = temp;
}
}
堆排序
- 思路:这种算法将数组理解为一棵树,下标可以反映他们的树形结构。规定下标为0的元素为根节点,下标为(02+1)的元素为左节点,下标为(02+2)的元素为右节点。下标为m的元素为根节点,下标为(2 * m + 1)的元素为其左节点,下标为(2 * m + 2)的元素为其右节点,根据这个规则我们可以很容易的将一个数组理解为一个树形结构。
此树形结构为:堆 堆分为大根堆(任何叶子节点必定比其父节点小)和小根堆(任何叶子节点必定比其父节点大),如果将一数组理解为一个堆,将数组的排序就可以理解为将堆转化为大根堆或者小根堆,便可以确定最大值。再依次将堆中的根节点(这里的根节点指的是总的根节点,即下标为0的元素)与堆的最后一个叶子节点交换,之后再次调整堆,使其保持为大根堆或小根堆。重复上述过程,直到所有节点完成交换,即完成排序。这个解释可能有点晦涩,看图解 - 图解
上图是将一个数组理解为堆的结构,当然,数据结构还是数组,只是我们将其理解为堆结构
上面是堆经过调整后的结果,现在他是一个大根堆,他的每个叶子都比其对应的根小
上图是将根节点与最后一个叶子节点交换的结果,可以看到2和9交换了位置
这是将上面的堆调整过后的结果,他除了最后一个叶子节点以外,是一个大根堆
重复上面的交换过程,交换完成后,再对堆进行调整,使只成为大根堆,直到根节点及结束 - 代码:
public static void heapSort(int[] arr) {
// 先将arr排序为大根堆
int count = arr.length;
for (int index = count / 2 - 1; index >= 0; index--) {
adjustHeap(arr, count, index);
}
// 再将root与最后一个交换
for (int index = 0; index < count; count--) {
int temp = arr[0];
arr[0] = arr[count - 1];
arr[count - 1] = temp;
adjustHeap(arr, count - 1, 0);
}
}
/**
* @param arr:给入的数组
* @param count:有效元素个数
* @param root:根节点下标
*/
private static void adjustHeap(int[] arr, int count, int root) {
while (root < count / 2) {
int leftIndex = root * 2 + 1; // 这是根节点的左节点
int rightIndex = root * 2 + 2; // 根节点的右节点
int maxIndex = rightIndex >= count ? leftIndex :
(arr[leftIndex] > arr[rightIndex] ? leftIndex : rightIndex); // 选出左右节点中的较大节点
if (arr[maxIndex] > arr[root]) { // 如果子节点中较大的节点的值比根节点要大,也就是说,这个子节点的值是三个节点中最大的值
int temp = arr[maxIndex]; // 那就将这个子节点和根节点交换,形成根节点最大的现象:大根堆
arr[maxIndex] = arr[root];
arr[root] = temp;
root = maxIndex;
} else {
return;
}
}
}
交换排序
冒泡排序
- 思路:依次遍历数组,比较并交换两个相邻的数字,保持较大的数字在靠后的位置,直至数组结尾,则数组尾部为本数组中的最大数字。这个过程也可以反过来,向前进行。
- 图解(图片来自网络):
- 代码:
public static void bubbleSort(int[] arr) {
int count;
for (count = arr.length; count > 0; count--) {
for (int i = 0; i < count - 1; i++) {
if (arr[i] > arr[i + 1]) {
int temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
}
}
快速排序
- 思路:以数组的第一个元素为分界点,将整个数组分为大于第一个元素的和小于第一个元素的两部分,再分别对分开的两部分进行同样的操作。
- 图解(图片来自网络):
- 代码:
public static void quickSort(int[] arr) {
innerQuickSort(arr, 0, arr.length - 1);
}
private static void innerQuickSort(int[] arr, int beginIndex, int endIndex) {
if (beginIndex >= endIndex) {
return;
}
// 进行交换的动作,需要返回一个标志,标志是数据分界点
int mid = swap(arr, beginIndex, endIndex);
innerQuickSort(arr, beginIndex, mid - 1);
innerQuickSort(arr, mid + 1, endIndex);
}
private static int swap(int[] arr, int beginIndex, int endIndex) {
int temp = arr[beginIndex];
while (beginIndex < endIndex) {
while (beginIndex < endIndex && arr[endIndex] >= temp) {
endIndex--;
}
if (beginIndex < endIndex) {
arr[beginIndex++] = arr[endIndex];
}
while (beginIndex < endIndex && arr[beginIndex] <= temp) {
beginIndex++;
}
if (beginIndex < endIndex) {
arr[endIndex--] = arr[beginIndex];
}
}
arr[beginIndex] = temp;
return beginIndex;
}