本文总结了7种最常用的排序算法。
一、冒泡排序
基本思想:通过元素的两两比较,每次比较,将较大的元素右移,第一次循环,可以将最大的元素移动到最右端,第二次循环,可以将第二大的元素移动到右边第二个位置,重复这个过程,直到整个数组有序。算法的时间复杂度是O(n^2)。代码如下:
public static void bubbleSort(int[] a) {
int n = a.length;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - 1; j++) {
int temp = 0;
if (a[j] > a[j + 1]) {
temp = a[j + 1];
a[j + 1] = a[j];
a[j] = temp;
}
}
}
}
二、快速排序
基本思想:找到一个基准数,一般是第一个数,将小于基准数的数“交换”到基准数的左边,将大于基准数的数“交换”到基准数的右边,这样一次循环后,基准数左边的数都小于基准数,基准数右边的数都大于基准数,之后用递归,对基准数的左边和右边进行同样的操作。需要注意的是,此处的“交换”并不是真的交换,而是将数覆盖到基准数的位置,因为基准数已经存在变量中了。时间复杂度为O(nlog2n)。
public static void quickSort(int[] a, int left, int right) {
if (left > right) {
return;
}
int base = a[left];
int i = left;
int j = right;
while (i < j) {
while (i < j && a[j] >= base) {
j--;
}
if (i < j) {
a[i] = a[j];
i++;
}
while (i < j && a[i] < base) {
i++;
}
if (i < j) {
a[j] = a[i];
j--;
}
}
a[i] = base;
quickSort(a, left, i - 1);
quickSort(a, i + 1, right);
}
三、插入排序
基本思想:首先将第一个元素当作一个有序序列,后面的元素为无序序列,从第二个元素开始,每次先把元素插入到有序序列中,再循环,通过比较大小两两交换,将这个元素放到有序的位置上,重复这个过程,直到整个序列有序。算法的时间复杂度为O(n^2);
public static void insertSort(int[] a) {
int n = a.length;
for (int i = 1; i < n; i++) {
for (int j = i; j > 0; j--) {
if (a[j] < a[j - 1]) {
int temp = a[j];
a[j] = a[j - 1];
a[j - 1] = temp;
}
}
}
}
四、希尔排序
基本思想:因为插入排序在数组基本有序的时候效率最高,在数组无序的时候效率降低,因此,在插入排序的基础上,出现了希尔排序。每次取一个增量,假设数组长度为n,一般初始的增量为n/2,根据这个增量将数组分为两组,每组里分别使用插入排序,需要注意的是,这里的分组其实是不连续的。之后将增加设置为n/4,重复上一个过程,直到数组有序。算法的时间复杂度是O(n^1.3)。
public static void shellSort(int[] a) {
int n = a.length;
int gap = n / 2;
while (gap != 0) {
for (int i = 0; i + gap < n; i += gap) {
for (int k = i + gap; k > 0; k -= gap) {
if (a[k] < a[k - gap]) {
int temp = a[k];
a[k] = a[k - gap];
a[k - gap] = temp;
}
}
}
gap = gap / 2;
}
}
五、简单选择排序
基本思想:第一次循环,找到最小的元素,将这个元素放到第一个位置,之后排除已经排好序的元素,将最小的元素放到最小的位置,以此类推。时间复杂度为O(n^2)。
int small;
for (int i = 0; i < a.length; i++) {
small = i;
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[small]) {
int temp = a[j];
a[j] = a[small];
a[small] = temp;
}
}
}
六、堆排序
基本思想:堆是一种数据结构,是一棵完全二叉树,对于其中的每个节点,其子节点都大于这个节点,是为小跟堆。大跟堆同理。进行升序排序,首先建立大顶堆,之后每次对堆进行最大堆调整。
最大堆调整:向下调整,每次将一个元素和它的子节点比较,如果子节点中比较大的那个比父节点大,就将父节点和子节点交换,然后以这个大的子节点为父节点,继续向下调整。
建立大顶堆:从最后一个非叶子结点开始,自下而上,自右而左进行最大堆调整。最后一个非叶子结点的下标为len/2-1。
整个过程:首先建立大顶堆,将堆顶和最后一个元素last交换,在对堆顶元素,以及剩下的n-1个元素进行一次最大堆调整,不断调整,直到最后一个元素last下表为0。
算法时间复杂度为O(NlogN)。
public static void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
//最大堆调整
public static void adjust(int[] a, int father, int len) {
int child = 2 * father + 1;
while (child < len) {
if (child + 1 < len && a[child] < a[child + 1]) {
child++;
}
if (a[child] > a[father]) {
swap(a, child, father);
//继续向下调整
father = child;
child = father * 2 + 1;
} else {
break;
}
}
}
//建立最大堆
public static void buildHeap(int[] a) {
int len = a.length;
for (int i = len / 2 - 1; i >= 0; i--) {
adjustHeap(a, i, len);
}
}
public static void heapSort(int[] a) {
buildHeap(a);
int len = a.length - 1;
while (len > 0) {
//交换最后一个,这里的len是下标
swap(a, 0, len);
//调整,这里的len是长度
adjustHeap(a, 0, len);
len--;
}
}
七、二路归并排序
基本思想:采用分治法的思想,将已有序的子序列合并,最终得到一个有序的序列。时间复杂度为O(nlogn)。首先递归地将一个数组分组,知道每个分组的长度为1;再递归地将数组合并成有序的数组。
//将两个分组合并成一个
public static void merge(int[] a, int start, int mid, int end) {
int[] temp = new int[end - start + 1];
int i = start, j = mid + 1;
int index = 0;
while (i <= mid && j <= end) {
if (a[i] <= a[j]) {
temp[index++] = a[i++];
} else if (a[i] > a[j]) {
temp[index++] = a[j++];
}
}
//如果左边有剩下的
while (i <= mid) {
temp[index++] = a[i++];
}
//如果右边有剩下的
while (j <= end) {
temp[index++] = a[j++];
}
int len = 0;
while (len < index) {
a[start++] = temp[len++];
}
}
//先递归分组,再合并,其中,start和end都是下标,从0开始
public static void mergeSort(int[] a, int start, int end) {
if (start >= end) {
return;
}
int mid = (start + end) / 2;
//分组
mergeSort(a, start, mid);
mergeSort(a, mid + 1, end);
//合并
merge(a, start, mid, end);
}