十大经典排序算法
如果遇到相等的值不进行交换,那这种排序方式是稳定的排序方式。
-
选择排序
-
插入排序
-
冒泡排序
-
非优化版本
-
优化版本
-
-
希尔排序
-
归并排序
-
递归式归并排序
-
非递归式归并排序
-
-
快速排序
-
堆排序
-
基数排序
-
非优化版本
-
优化版本
-
-
桶排序
-
基数排序
1、选择排序
过程简单描述:
首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。其次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。这种方法我们称之为选择排序。
1 public class SelectSort {
2 public static int[] selectSort(int[] a) {
3 int n = a.length;
4 for (int i = 0; i < n - 1; i++) {
5 int min = i;
6 for (int j = i + 1; j < n; j++) {
7 if(a[min] > a[j]) min = j;
8 }
9 //交换
10 int temp = a[i];
11 a[i] = a[min];
12 a[min] = temp;
13 }
14 return a;
15 }
16}
性质:1、时间复杂度:O(n2) 2、空间复杂度:O(1) 3、非稳定排序 4、原地排序
2、插入排序
假设数列第一个元素为已排序数列,剩余数列为未排序将待排序元素挨个插入到已排序数列中每次插入都必须保证数列是有序的,即通过比较和移动有序数列中的元素,将元素插入到合适的位置
思路:如同玩扑克牌一样,每次摸牌都将它与手中的牌比较,始终将牌放在比它大的牌前面,比它小的牌后面。这样当牌全部摸到手上后,就是一个有序的序列。从后往前找合适的位置。
public class MainActivity extends AppCompatActivity {
int[] arr = new int[]{9, 5, 4, 8, 7, 3, 1};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int[] insert = insert(arr);
System.out.println(Arrays.toString(insert));
}
public int[] insert(int[] array) {
for (int i = 1; i < array.length; i++) {
if (array[i] < array[i - 1]) {//如果第i个数大于前一个数就不用判断了,因为前面都是有序数列,大于前一个数肯定比前面所有数都大,否则的话把这个数拿出来也就是赋值给temp,然后依次与前面的数比较,如果比前一个数小就让前一个数往后挪一位直到找到比temp小的位置放进去
int temp = array[i];
int f = i;
for (; f >= 1 && array[f - 1] > temp; f--) {
array[f] = array[f-1];
}
array[f] = temp;
}
}
return array;
}
}
3、冒泡排序
1.原理:比较两个相邻的元素,将值大的元素交换到右边
2.思路:依次比较相邻的两个数,将比较小的数放在前面,比较大的数放在后面。
(1)第一次比较:首先比较第一和第二个数,将小数放在前面,将大数放在后面。
(2)比较第2和第3个数,将小数 放在前面,大数放在后面。
......
(3)如此继续,知道比较到最后的两个数,将小数放在前面,大数放在后面,重复步骤,直至全部排序完成
(4)在上面一趟比较完成后,最后一个数一定是数组中最大的一个数,所以在比较第二趟的时候,最后一个数是不参加比较的。
(5)在第二趟比较完成后,倒数第二个数也一定是数组中倒数第二大数,所以在第三趟的比较中,最后两个数是不参与比较的。
(6)依次类推,每一趟比较次数减少依次
3.举例:
(1)要排序数组:[10,1,35,61,89,36,55]
性质:1、时间复杂度:O(n2) 2、空间复杂度:O(1) 3、稳定排序 4、原地排序
4、希尔排序
是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2...1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。
1 public class ShellSort {
2 public static int[] shellSort(int arr[]) {
3 if (arr == null || arr.length < 2) return arr;
4 int n = arr.length;
5 // 对每组间隔为 h的分组进行排序,刚开始 h = n / 2;
6 for (int h = n / 2; h > 0; h /= 2) {
7 //对各个局部分组进行插入排序
8 for (int i = h; i < n; i++) {
9 // 将arr[i] 插入到所在分组的正确位置上
10 insertI(arr, h, i);
11 }
12 }
13 return arr;
14 }
15
16 /**
17 * 将arr[i]插入到所在分组的正确位置上
18 * arr[i]] 所在的分组为 ... arr[i-2*h],arr[i-h], arr[i+h] ...
19 */
20 private static void insertI(int[] arr, int h, int i) {
21 int temp = arr[i];
22 int k;
23 for (k = i - h; k > 0 && temp < arr[k]; k -= h) {
24 arr[k + h] = arr[k];
25 }
26 arr[k + h] = temp;
27 }
28}
性质:1、时间复杂度:O(nlogn) 2、空间复杂度:O(1) 3、非稳定排序 4、原地排序
5、归并排序
归并排序是一个典型的基于分治的递归算法。它不断地将原数组分成大小相等的两个子数组(可能相差1),最终当划分的子数组大小为1时,将划分的有序子数组合并成一个更大的有序数组。
public static void mergeSort(int[] arr) {
sort(arr, 0, arr.length - 1);
}
public static void sort(int[] arr, int L, int R) {
if(L == R) {
return;
}
int mid = L + ((R - L) >> 1);
sort(arr, L, mid);
sort(arr, mid + 1, R);
merge(arr, L, mid, R);
}
public static void merge(int[] arr, int L, int mid, int R) {
int[] temp = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = mid + 1;
// 比较左右两部分的元素,哪个小,把那个元素填入temp中
while(p1 <= mid && p2 <= R) {
temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
// 上面的循环退出后,把剩余的元素依次填入到temp中
// 以下两个while只有一个会执行
while(p1 <= mid) {
temp[i++] = arr[p1++];
}
while(p2 <= R) {
temp[i++] = arr[p2++];
}
// 把最终的排序的结果复制给原数组
for(i = 0; i < temp.length; i++) {
arr[L + i] = temp[i];
}
}
性质:1、时间复杂度:O(nlogn) 2、空间复杂度:O(n) 3、稳定排序 4、非原地排序
6、快速排序
快速排序采用双向查找的策略,每一趟选择当前所有子序列中的一个关键字作为枢纽轴,将子序列中比枢纽轴小的前移,比枢纽轴大的后移,当本趟所有子序列都被枢轴按上述规则划分完毕后将会得到新的一组更短的子序列,他们将成为下趟划分的初始序列集。
1 public class QuickSort {
2 public static int[] quickSort(int[] arr, int left, int right) {
3 if (left < right) {
4 //获取中轴元素所处的位置
5 int mid = partition(arr, left, right);
6 //进行分割
7 arr = quickSort(arr, left, mid - 1);
8 arr = quickSort(arr, mid + 1, right);
9 }
10 return arr;
11 }
12
13 private static int partition(int[] arr, int left, int right) {
14 //选取中轴元素
15 int pivot = arr[left];
16 int i = left + 1;
17 int j = right;
18 while (true) {
19 // 向右找到第一个小于等于 pivot 的元素位置
20 while (i <= j && arr[i] <= pivot) i++;
21 // 向左找到第一个大于等于 pivot 的元素位置
22 while(i <= j && arr[j] >= pivot ) j--;
23 if(i >= j)
24 break;
25 //交换两个元素的位置,使得左边的元素不大于pivot,右边的不小于pivot
26 int temp = arr[i];
27 arr[i] = arr[j];
28 arr[j] = temp;
29 }
30 arr[left] = arr[j];
31 // 使中轴元素处于有序的位置
32 arr[j] = pivot;
33 return j;
34 }
35}
性质:1、时间复杂度:O(nlogn) 2、空间复杂度:O(logn) 3、非稳定排序 4、原地排序
7、堆排序
基本思想:
1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
//堆排序
public static void heapSort(int[] arr) {
//构造大根堆
heapInsert(arr);
int size = arr.length;
while (size > 1) {
//固定最大值
swap(arr, 0, size - 1);
size--;
//构造大根堆
heapify(arr, 0, size);
}
}
//构造大根堆(通过新插入的数上升)
public static void heapInsert(int[] arr) {
for (int i = 0; i < arr.length; i++) {
//当前插入的索引
int currentIndex = i;
//父结点索引
int fatherIndex = (currentIndex - 1) / 2;
//如果当前插入的值大于其父结点的值,则交换值,并且将索引指向父结点
//然后继续和上面的父结点值比较,直到不大于父结点,则退出循环
while (arr[currentIndex] > arr[fatherIndex]) {
//交换当前结点与父结点的值
swap(arr, currentIndex, fatherIndex);
//将当前索引指向父索引
currentIndex = fatherIndex;
//重新计算当前索引的父索引
fatherIndex = (currentIndex - 1) / 2;
}
}
}
//将剩余的数构造成大根堆(通过顶端的数下降)
public static void heapify(int[] arr, int index, int size) {
int left = 2 * index + 1;
int right = 2 * index + 2;
while (left < size) {
int largestIndex;
//判断孩子中较大的值的索引(要确保右孩子在size范围之内)
if (arr[left] < arr[right] && right < size) {
largestIndex = right;
} else {
largestIndex = left;
}
//比较父结点的值与孩子中较大的值,并确定最大值的索引
if (arr[index] > arr[largestIndex]) {
largestIndex = index;
}
//如果父结点索引是最大值的索引,那已经是大根堆了,则退出循环
if (index == largestIndex) {
break;
}
//父结点不是最大值,与孩子中较大的值交换
swap(arr, largestIndex, index);
//将索引指向孩子中较大的值的索引
index = largestIndex;
//重新计算交换之后的孩子的索引
left = 2 * index + 1;
right = 2 * index + 2;
}
}
//交换数组中两个元素的值
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
性质:1、时间复杂度:O(nlogn) 2、空间复杂度:O(1) 3、非稳定排序 4、原地排序
8、计数排序
计数排序是一种适合于最大值和最小值的差值不是很大的排序。
基本思想:就是把数组元素作为数组的下标,然后用一个临时数组统计该元素出现的次数,例如 temp[i] = m, 表示元素 i 一共出现了 m 次。最后再把临时数组统计的数据从小到大汇总起来,此时汇总起来是数据是有序的。
性质:1、时间复杂度:O(n+k) 2、空间复杂度:O(k) 3、稳定排序 4、非原地排序
优化一下
上面的代码中,我们是根据 max 的大小来创建对应大小的数组,假如原数组只有10个元素,并且最小值为 min = 10000,最大值为 max = 10005,那我们创建 10005 + 1 大小的数组不是很吃亏,最大值与最小值的差值为 5,所以我们创建大小为6的临时数组就可以了。
也就是说,我们创建的临时数组大小 (max - min + 1)就可以了,然后在把 min作为偏移量。优化之后的代码如下所示:
1public class Counting {
2 public static int[] sort(int[] arr) {
3 if(arr == null || arr.length < 2) return arr;
4
5 int n = arr.length;
6 int min = arr[0];
7 int max = arr[0];
8 // 寻找数组的最大值与最小值
9 for (int i = 1; i < n; i++) {
10 if(max < arr[i])
11 max = arr[i];
12 if(min > arr[i])
13 min = arr[i];
14 }
15 int d = max - min + 1;
16 //创建大小为max的临时数组
17 int[] temp = new int[d];
18 //统计元素i出现的次数
19 for (int i = 0; i < n; i++) {
20 temp[arr[i] - min]++;
21 }
22 int k = 0;
23 //把临时数组统计好的数据汇总到原数组
24 for (int i = 0; i < d; i++) {
25 for (int j = temp[i]; j > 0; j--) {
26 arr[k++] = i + min;
27 }
28 }
29 return arr;
30 }
31}
9、桶排序
10、基数排序
上一篇: 十大经典排序算法
下一篇: Ext.create使用(下)