1. 选择排序
选择排序就是在遍历数组的过程中,每次都选出从当前位置到数组末尾的最小值,并把选出来的最小值和当前位置交换。
//选择排序
public static void select_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
for(int i = 0; i < n; i++){
int minIndex = i;
for(int j = i + 1; j < n; j++){
min = arr[minIndex] > arr[j] ? j : minIndex;
}
if(minIndex != i) swap(arr, i, minIndex);
}
}
- 时间复杂度:O(n的平方)
- 空间复杂度:O(1)
- 不稳定
2. 冒泡排序
冒泡排序就是在遍历数组的时候,让当前位置元素arr[j] 和后一个元素arr[j+1] 进行比较,将大的元素放在后面,最后遍历完,最后一个元素就是最大值,下一次遍历,只需到上一次最大值的位置(不包括这个位置),这样,每次找到的都是每一段元素中的最大值。
//冒泡排序
public static void bubble_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
for(int i = n - 1; i >= 0; i--){
//注意遍历到i,不包括i,因为i位置是最大值
for(int j = 0; j < i; j++){
if(arr[j] > arr[j + 1]) swap(arr, j, j + 1);
}
}
}
- 时间复杂度:O(n的平方)
- 空间复杂度:O(1)
- 可以做到稳定
3. 插入排序
插入排序就是遍历数组的时候,假设当前位置之前的数组都是有序的,如果当前位置的数小于前一个数就交换,如果不小于,就结束插入,继续遍历数组中下一个位置
//插入排序
public static void insert_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
//注意这里是从1开始的
for(int i = 1; i < n; i++){
//若果arr[j] >= arr[j-1],这个循环就结束了
for(int j = i; j > 0 && arr[j] < arr[j - 1]; j--){
swap(arr, j, j - 1);
}
}
}
- 时间复杂度:O(n的平方)
- 空间复杂度:O(1)
- 可以做到稳定
4. 归并排序
归并排序就是利用分治思想,先将数组切成两半,然后合并。利用递归,将数组切成两半,再将每一半切成两半,…,最后分成每一半只有一个元素,然后,一半一半(这两半是同一次拆分成的)的合并,合并的过程中排序。
//归并排序
public static void merge_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
merge_sort(arr, 0, arr.length - 1);
}
private static void merge_sort(int[] arr, int left, int right){
//切到只有一个元素
if(left >= right) return;
int mid = left + (right - left) / 2;
merge_sort(arr, left, mid);
merge_sort(arr,mid + 1, right);
merge(arr, left, mid, right);
}
//合并,left为左半部分开始的索引,right为右半部分结束的索引
private static void merge(int[] arr, int left, int mid, int right){
//临时数组,暂时存放数据
int[] help = new int[right - left + 1];
int count = 0;
//i为左半部分的开始,j为右半部分的开始
int i = left, j = mid + 1;
//将左半部分和右半部分进行比较,较小的数先放到数组
while(i <= mid && j <= right){
help[count++] = arr[i] > arr[j] ? arr[j++]:arr[i++];
}
//可能有左半部分或者右半部分的数没有存到help数组
//判断左半部分是否都已存到help数组,没有的话,就存到数组
while(i <= mid){
help[count++] = arr[i++];
}
//判断右半部分是否都已存到数组,没有的话,就存到数组
while(j <= right){
help[count++] = arr[j++];
}
//将排好序的数组放到原数组中
for(int k = 0; k < help.length; k++){
arr[left++] = help[k];
}
}
- 时间复杂度:O(nlogn)
- 空间复杂度:O(n)
- 可以做到稳定
5. 快速排序
快速排序就是找出一个参照物,每次将大于参照物的放在右边,小于参照物的放在左边,之后,再分别从参照为的两边找参照物,重复上述过程,整个过程也是递归的。
//快速排序
public static void quick_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
quick_sort(arr,0, arr.length - 1);
}
private static void quick_sort(int[] arr, int left, int right){
if(left >= right) return;
//快速排序的效率跟选的参照物有很大的关系,所以,这里,随机选一个,和数组
//首元素交换
swap(arr, left, left + (int)(Math.random() * (right - left + 1)));
int mid = partition(arr, left, right);
quick_sort(arr, left, mid - 1);
quick_sort(arr, mid + 1, right);
}
private static int partition(int[] arr, int left, int right){
int model = arr[left];
while(left < right){
//当arr[right]>model的时候,让right一直左移
//注意这里要判断left<right,有可能在移动的时候,left = right
while(left < right && arr[right] > model){
right--;
}
//上面循环结束,可能是nums[right]<model,这样的话,就交换left和right的值
//也可能是left == right,所以判断一下
if(left < right){
arr[left++] = arr[right];
}
while(left < right && arr[left] < model){
left++;
}
if(left < right) {
arr[right--] = arr[left];
}
}
//当right=left的时候,这个位置是空,把model放进去
arr[left] = model;
return left;
}
- 时间复杂度:O(nlogn)
- 空间复杂度:O(logn)
- 常规实现不稳定
快速排序的时间复杂度主要与选取的参照物有关,可以随机选出一个值作为参照物,下面是快速排序的另一种写法,这种写法和上面哪一种写法的partition过程不一样。
//快速排序
public static void quick_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
quick_sort(arr,0, arr.length - 1);
}
private static void quick_sort(int[] arr, int left, int right){
if(left >= right) return;
//快速排序的效率跟选的参照物有很大的关系,所以,这里,随机选一个,和数组
//首元素交换
//swap(arr, left, left + (int)(Math.random() * (right - left + 1)));
//随机选一个数,和数组最后一个元素交换,作为参照物
swap(arr, right, left + (int)(Math.random() * (right - left + 1)));
//int mid = partition(arr, left, right);
int mid = partition2(arr, left, right);
quick_sort(arr, left, mid - 1);
quick_sort(arr, mid + 1, right);
}
private static int partition2(int[] arr, int left, int right) {
//小于等于target的边界
int small = left - 1;
//大于target的边界,注意最后一个数是target,最后交换
int big = right;
while(left < big){
if(arr[left] < arr[right]){
//注意left++
swap(arr, ++small, left++);
}else if(arr[left] > arr[right]){
swap(arr, --big, left);
}else{
left++;
}
}
//最后,将arr[big]和比较的值arr[right]交换
swap(arr, big, right);
//最后返回big的索引
return big;
}
快速排序的第三种写法
6. 堆排序
数据结构中的堆是一个完全二叉树,一个数组,可以看成是一个完全二叉树,按层次遍历的顺序存放,一个元素(索引为i)的父节点是 (i- 1)/ 2, 左孩子的索引为(2 * i + 1)。如果堆中,每一个根节点(子树中)都是所在子树中的最大值,那么这个堆就是最大堆,堆排序就是利用最大堆进行排序的。大致过程是,将数组构建成一个最大堆,将数组最后一个元素(n -1)和第一个元素交换,再将除最后一个元素之外的数组调整为最大堆,重复上述过程。
//堆排序
public static void heap_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
//构造最大堆
for(int i = 0; i < arr.length; i++){
heap_insert(arr, i);
}
int size = arr.length;
//将第一个元素和最后一个元素交换
swap(arr, 0, --size);
while(size > 0){
//调整堆为最大堆
heapify(arr, 0, size);
swap(arr, 0, --size);
}
}
//使用插入的方式构建最大堆
private static void heap_insert(int[] arr, int index){
//当孩子结点大于父节点的时候交换
while(arr[index] > arr[(index - 1) / 2]){
swap(arr, index, (index - 1) / 2);
//父节点和子节点交换完之后,将父节点的索引赋给index,
//再去判断父节点和父节点的父节点的大小,所以这里用的是while
index = (index - 1) / 2;
}
}
//调整堆,注意size是数组个数,不是索引
private static void heapify(int[] arr, int index,int size){
int left = index * 2 + 1;
while(left < size){
int largest = left;
//找出左右孩子中最大的一个
if(left + 1 < size && arr[left + 1] > arr[left]){
largest++;
}
//如果根节点大于左右孩子,那么说明已经是最大堆了,
//因为左右孩子是左右子树的根节点,是子树中的最大值
if(arr[index] > arr[largest]){
break;
}
swap(arr, index, largest);
//将左右子树中最大值的索引赋给index,调整子树为最大堆
index = largest;
left = index * 2 + 1;
}
}
- 时间复杂度:O(nlogn)
- 空间复杂度:O(1)
- 不稳定
另外一种写法
构建堆的过程 ,其实也是调整堆的过程,从最后一个根节点开始,保证所有子树都是一个小根堆,那么整个树就是小根堆了
public static void heapSort(int[] arr) {
//构建最大堆
for(int i = arr.length / 2 - 1; i >= 0; i--) {
adjust(arr, i, arr.length);
}
for(int i = arr.length - 1; i >= 0; i--) {
swap(arr, 0, i);
adjust(arr, 0, i);
}
}
public static void adjust(int[] arr, int i, int size) {
int left = 2 * i + 1;
while(left < size) {
//temp为i的左右孩子中最大的一个的索引
int temp = left;
//如果有右孩子,并且右孩子大于左孩子,就把左孩子索引赋给temp
if(left + 1 < size && arr[left + 1] > arr[left]) {
temp = left + 1;
}
//判断最小值和根节点哪个大
if(arr[i] > arr[temp]) {
break;
}
//如果小于,就交换
swap(arr, i, temp);
//将temp赋给i,调整子树,因为,子树的根节点变了,所以,也要调整
i = temp;
left = temp * 2 + 1;
}
}
7. 计数排序
计数排序其实是一种特殊的桶排序,就是先求出数组中的最大值max和最小值min,然后,建max-min +1个桶,假入是数组,数组长度就是max-min+1,bucket数组中存的就是原数组中第i(i为桶的索引,数组元素-min得到)小的数的个数,统计完之后,根据桶(bucket)中的个数,将min+i放到原数组中,对应的桶中记录的有几个就放几个。计数排序和前面六中排序不同的是,计数排序不是基于比较的排序。
//计数排序
public static void count_sort(int [] arr){
if(arr == null || arr.length < 2) return;
int min = arr[0];
int max =0;
//找出数组中的最大值和最小值
for(int i = 0; i < arr.length; i++){
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
//bucket数组中存放的是第i小的个数
int[] bucket = new int[max - min + 1];
//统计元素出现的次数
for(int i = 0; i < arr.length; i++){
bucket[arr[i] - min]++;
}
int j = 0;
//将bucket中的数写回原数组
for(int i = 0; i < bucket.length; i++){
while(bucket[i]-- > 0){
arr[j++] = min + i;
}
}
}
不稳定的排序:快(快排)些(希尔)选(选择)一堆(堆排)
8. java中的Arrays.sort()
java1.8中,当数组长度小于47的时候,使用的是冒泡排序,当数组长度大于47,小于286的时候使用双轴快速排序,并且当快速排序递归时,如果长度小于47,直接使用插入排序,大于286的时候,使用的是归并排序。