## 排序的分类:
1、直接插入排序
每次从无序区间选择第一个元素插入有序区间的合适位置
因为在插入元时,小于等于的前一个不插,是稳定的排序,时间复杂度为O(n^2)
在近乎有序的数组中性能非常好
public static void insertSort(int[] arr){
//默认第一个元素是有序的
//无序区间
for (int i = 1; i < arr.length; i++) {
//有序区间[i,arr.length)
// 有序区间[0,i)
for (int j=i; j >0; j--) {
//j为无序区间的第一个元素,j -1为有序区间最后一个元素(有序区间最大的元素),所以arr[j]>arr[j-1]时循环结束
if(arr[j]<arr[j-1]){
swap(arr,j,j-1);
}
else {
break;
}
}
//System.out.println(Arrays.toString(arr));
}
}
**优化:**在插入元素时,可以用折半查找法在有序的元素区间进行查找
折半插入排序:
public static void insertSortBS(int[] arr){
//无序区间
for (int i = 1; i < arr.length; i++) {
int val=arr[i];//无序区间的第一个值
int low=0,high=i;
while (low<high){
int mid=(low+high)/2;
if(val>=arr[mid]){
low=mid+1;
}
//右区间取不到,不用-1
else{
high=mid;
}
}
//数据搬移
for (int j = i; j >low; j--) {
arr[j]=arr[j-1];
}
//low为元素插入位置
arr[low]=val;
System.out.println(Arrays.toString(arr));
}
}
2、希尔排序
选定一个整数gap,将待排序数组中按gap排序,所有举例为gap的元素放在一组,在组减进行排序,然后不断缩小gap直到为1,数组有序。
希尔排序为不稳定的排序,时间复杂度为O(n^2)
//根据间隔两两进行比较
public static void shellSort(int[] arr){
//先确定间距为数组长度的一半
int gap=arr.length/2;
//间距为1时停止
while (gap>1){
insertSortShell(arr,gap);
gap/=2;
}
insertSortShell(arr,1);
}
//希尔排序中的选择排序
private static void insertSortShell(int[] arr, int gap) {
//两个元素交换是右边的元素
for (int i = gap; i < arr.length; i++) {
//两个元素交换时左边的元素
//在内层循环中,凡是满足间隔的一组数循环反复交换‘’‘
for (int j = i; j-gap>=0 && arr[j]<arr[j-gap] ; j=j-gap) {
swap(arr,j,j-gap);
}
}
}
3、选择排序
每一次从无需区间选择最大(最小)的元素放在无序区间的最后(最前),直到全部待排序数据匀速排完
选择排序是不稳定的排序,时间复杂度为O(n^2)
public static void selectSort(int[] arr){
//每次从数组中选择最小或最大的放在数组头部或尾部
//最外层循环表示执行的总次数
//已经有序的集合[0,i) 待排序的集合[i+1,n)
//每次排序,已经排序的集合元素个数+1,待排序的集合元素个数-1
for (int i = 0; i < arr.length; i++) {
int min=i;//存储最小值元素的下标,默认为i
//此循环从外层循环的下一个元素开始进行内层循环
for (int j = i+1; j < arr.length; j++) {
if(arr[min]>arr[j]){
min=j;//遇到比默认值小的元素将min进行更新
}
}
//此时min存储的是最小元素的下标,将min于默认的最小元素交换
swap(arr,i,min);
}
}
public static void swap(int[] arr,int i,int min){
int temp=arr[i];
arr[i]=arr[min];
arr[min]=temp;
}
优化:双向选择排序,一趟选出最大和最小两个元素,大的放后,小的放前
双向选择排序:
public static void selectSortDouble(int[] arr){
int low=0,high=arr.length-1;
while (low<high){
int min=low,max=low;
for (int i = low+1; i <= high; i++) {
if(arr[i]<arr[min]){
min=i;
}
if(arr[i]>arr[max]){
max=i;
}
}
//此循环走完后,min和max分别存储最小元素下标和最大元素下标
swap(arr,low,min);
//此时有特殊情况:如果第一个元素为最大值元素,最小值元素进行交换的时候将之前确定好的元素下标改变
// 此时就要更新最大值元素下标为换走的那个最小值元素下标
//要是没有这个特殊情况,不用进入此if
if(max==low){
max=min;
}
swap(arr,high,max);
low=low+1;
high=high-1;
}
}
4、堆排序
步骤:
(1)先堆化,从最后一个飞叶子结点开始,将小元素下沉,建成最大堆,这样堆顶为最大值元素;
(2)从元素最后一个叶子结点开始,与堆首的元素(最大值元素)进行交换,再进行下沉操作,将大的元素发放在后面,直到所有元素都与堆首元素进行交换,形成升序
堆排序是不稳定的排序算法,时间复杂度为O(nlogn)
public static int parent(int k){
return (k-1)/2;
}
public static int left(int k){
return 2*k+1;
}
public static int right(int k){
return 2*k+2;
}
//每次先下沉,将最大元素升上去,小元素降下去,再交换堆首与最后一个叶子结点,再下沉,反复,形成升序
public static void heapSort(int[] arr){
//首先将数组调整为最大堆,从对的最后一个非叶子结点开始
for (int i = 0; i < (arr.length-1)/2; i++) {
siftDown(arr,i,arr.length);
}
//交换堆顶的元素和堆中的最后一个元素
for (int i = arr.length-1; i >=0 ; i--) {
swap(arr,i,0);
siftDown(arr,0,i);
}
}
//元素的下沉操作(最大堆),i为要下沉的堆元素,n为数组的长度
private static void siftDown(int[] arr,int i,int n){
//从最后一个非叶子结点开始,判断这个结点是否还有下沉的余地
while ((2*i)+1<n){
int j=left(i);//做孩子索引
//让j一直保持左右孩子中的最大值,此时注意有有孩子才能进入此循环
if(j+1<n && arr[j]<arr[j+1]){
j=j+1;
}
//将小的元素沉下去,形成最大堆
if(arr[i]<arr[j]){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
else {
break;
}
}
}
5、冒泡排序
在一趟中,紧挨的两个元素进行交换,将大的元素放在前,小的放在后,两两不断交换,直到一趟的元素都交换完成,再进行第二趟,一共进行arr.length-1趟
冒泡排序是稳定的排序,时间复杂度为0(n^2)
//冒泡排序
public static void bubbleSort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
for (int j = i+1; j < arr.length; j++) {
if(arr[i]>arr[j]){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
6、快速排序
(1)每次默认数组中第一个元素为key,i从数组最左开始,j从数组最由开始,当i>key时,i与key交换,否则i++;当j<key时,i与key交换,否则j–,直到l=j时,停止
(2)上述步骤将数组以key分为小数组,再在小数组中重复上述步骤,不断递归,直到数组被分为1个元素,递归结束
public static void quickSort(int[] arr){
qsort(arr,0,arr.length-1);
}
private static int[] qsort(int arr[],int start,int end) {
int pivot = arr[start];
int i = start;
int j = end;
while (i<j) {
while ((i<j)&&(arr[j]>pivot)) {
j--;
}
while ((i<j)&&(arr[i]<pivot)) {
i++;
}
if ((arr[i]==arr[j])&&(i<j)) {
i++;
} else {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
if (i-1>start){
arr=qsort(arr,start,i-1);
}
if (j+1<end) {
arr=qsort(arr,j+1,end);
}
return arr;
}
优化策略:
(1)当递归的数组元素个数<=15,用插入排序
(2)key的取值选取随机数,选出的随机数再与第一个位置的元素交换,开始比较
7、归并排序
(1)将原数组不断进行拆分,拆分到每个数组只剩下一个元素
(2)merge:将拆分的数组不断进行合并,在合并的过程中进行排序,直到整个数组都合并完,数组有序
//归并排序:先拆分为一个一个的元素,再一个一个合并
public static void mergeSort(int[] arr){
mergeSortInternal(arr,0,arr.length-1);
}
//在l到r上进行归并排序
private static void mergeSortInternal(int[] arr,int l,int r){
if(l>=r){
//此时只有一个元素或者不符合逻辑
return;
}
//找出中间位置,从中间将数组不断拆分
int mid=l+((r-l)>>1);//防止溢出
mergeSortInternal(arr,l,mid);//排序左区间
mergeSortInternal(arr,mid+1,r);//排序右区间
//此时左右区间都有序,只需合并两个区间
merge(arr,l,mid,r);
}
//将有序的两个数组合并为大的有序数组(l...mid,mid+1...r)
//这个方法具体实现两个数组怎样合并为一个有序数组
private static void merge(int[] arr, int l, int mid, int r) {
int[] temp=new int[r-l+1];
//将原数组的元素拷贝到新数组,此新数组用来存放左右排序好的数组,以便于直接在最初的数组上进行替换
for (int i = l; i <= r; i++) {
//新数组的索引和原数组的索引有l个单位的偏移量
//arr从1000开始,temp[0]=arr[1000]
//arr为原数组的部分,temp为复制原部分数组的一个数组,所以arr的索引比temp的大写,temp就比arr向右偏移l个,就需要减
temp[i-l]=arr[i];
}
//遍历两个数组,将两个数组进行有序合并:将左右两个数组最小值写会原数组
int i=l;//左有序区间的第一个索引
int j=mid+1;//左有序区间的第一个索引
for (int k = l; k <= r; k++) {
//此时左区间走完,将右区间所有值写会原数组
if(i>mid){
arr[k]=temp[j-l];
j++;
}
//此时右区间走完,将左区间所有值写会原数组
else if(j>r){
arr[k]=temp[i-l];
i++;
}
//此时左区间的元素小于右区间的元素 <=让此排序稳
else if(temp[i-l]<=temp[j-l]){
arr[k]=temp[i-l];
i++;
}
else {
arr[k]=temp[j-l];
j++;
}
}
}