前言
排序 : 就是将一串记录, 按照其中某个关键字的大小 , 递增或递减的操作
稳定性 : 排完序后, 原来相同的数据,顺序不变 - 这种算法被称为稳定的
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
插入排序
思想 : 类似于打扑克 , 第一个数据有序, 后序的数据逐个与前面数据比较,从而插入到合适的位置中去
/**
* 直接插入排序-推荐使用
* 时间复杂度O(N^2)
* 最好的情况是O(N):对于直接插入排序来说,最好的情况就是数据有序的时候
* 可以推导出结论:当数据越有序,时间效率越高
* 空间复杂度O(1)
* 稳定性:稳定的
* 一个稳定的排序,可以实现为不稳定的排序,但是一个本身不稳定的排序,是不可以
* 变成稳定的排序的。
* @param array
*/
public void insertSort(int[] array){
// 思想:假定第一个元素有序,从第二个元素开始回退排序
for (int i = 1; i < array.length; i++) {
int tmp=array[i];
int j = i-1;
for (; j >=0 ; j--) {
if (array[j]>array[j+1])array[j+1]=array[j];
// 当array[j]<tmp的时候,说明有序了
else break;
// 为什么不在else中写 array[j+1]=tmp;因为当j为-1的时候进不来
}
//j回退到了小于0的地方
array[j+1]=tmp;
}
}
希尔排序
思想 : 对插入排序进行优化 , 因为插入排序有个越有序效率越高的特性 ,所以希尔排序就是先将数据尽可能的小部分有序,从而达到整体有序
/**
* 希尔排序,
* 时间复杂度和增量有关系【n^1.3-n^1.5】
* 空间复杂度O(1)
* 稳定性:不稳定
* @param array
* @param gap
*/
//待排序的序列
private void shell(int [] array,int gap){
for (int i =gap; i < array.length; i++) {
int tmp=array[i];
int j = i-gap;
for (;j >0 ; j-=gap) {
if (array[j]>tmp)array[j+gap]=array[j];
else break;
}
array[j+gap]=tmp;
}
}
public void shellSort(int[] array){
int gap=array.length;
while (gap>1){
shell(array,gap);
gap/=2;
}
shell(array,1);
}
选择排序
思想 : 选择排序在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后以此类推,直到所有元素均排序完毕。
/**
* 选择排序-不推荐使用-很垃圾的
* 时间复杂度O(N^2)
* 稳定性:不稳定
* @param array
*/
public void choiceSort(int[] array){
for (int i = 0; i < array.length; i++) {
for (int j = i+1; j < array.length; j++) {
if (array[j]<array[i]){
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
}
}
}
堆排序
思想: 如果是升序数组的话 , 建立一个大根堆 , 每次从大根堆弹出堆顶元素 与 数组最后一个元素进行交换, 交换完成后,再向下调整.重复这个过程从而达到数组有序的状态
/**堆排序
* 时间复杂度O(NlogN)
* @param array
*/
public void heatSort(int[] array){
createHeap(array);
int end=array.length-1;
while (end>0){
int tmp=array[0];
array[0]=array[end];
array[end]=tmp;
shiftDown(array,0,end);
end--;
}
}
private static void createHeap(int[] array){
for (int parent = (array.length-1-1)/2; parent >=0 ; parent--) {
shiftDown(array,parent,array.length);
}
}
private static void shiftDown(int[] array,int parent,int len){
int child=2*parent+1;
while (child<len){
if (child+1<len&&array[child]<array[child+1]){
child++;//child下标就是左右孩子最大值的下标
}
if (array[child]>array[parent]){
int tmp=array[child];
array[child]=array[parent];
array[parent]=tmp;
parent=child;
child=2*parent+1;
}else break;
}
}
冒泡排序
思想 :不断比较左右的值,然后将最大的值放到最右边
/**冒泡排序
* 时间复杂度O(N^2)
* 空间复杂度)O(1)
* 稳定性:稳定
* @param array
*/
public void bubbleSort(int[] array){
for (int i = 0; i <array.length ; i++) {
//i是趟数
boolean flg=false;
for (int j = 0; j <array.length-1; j++) {
if (array[j]>array[j+1]){
int tmp=array[j];
array[j]=array[j+1];
array[j+1]=tmp;
flg=true;
}
}
if (flg==false)break;
}
}
快速排序
思想: 选择一个数, 将这个数组中 所有比这个基数大的数放到这个数的右边 , 所有比这个基数小的数放到这个数的左面, 然后 从这个数一分为二的数组中, 在重新寻找基数重术上述的过程
写法 : 推荐 - 挖坑法 , hore 法 , 前后指针法
优化: ①递归改成栈 , ②三数取中法(基数选择不在总是左右) ③对于小数列的数据用插入排序进行优化
话不多说 , 线上代码,下面具体解释三种具体写法
/**快速排序
* 时间复杂度(最好)【每次可以均匀分割待排序序列】:O(Nlogn)
* 最坏【数据有序,或者逆序的情况O(N^2);
* 空间复杂度:(logn)
* 最坏【单分支的一棵树】O(N)
* 稳定性:不稳定
* @param array
*/
public void FquickSort(int[] array){
//非递归实现
Stack<Integer> stack=new Stack<>();
int left=0;
int right=array.length-1;
int pivot=partition(array,left,right);
if (pivot >left+1){
stack.push(left);
stack.push(pivot-1);
}
if (pivot<right-1){
stack.push(pivot+1);
stack.push(right);
}
while (!stack.isEmpty()){
right=stack.pop();
left= stack.pop();
pivot=partition(array,left,right);
if (pivot>left+1){
stack.push(left);
stack.push(pivot-1);
}
if (pivot<right-1){
stack.push(pivot+1);
stack.push(right);
}
}
}
private void quick(int[] array,int left,int right){
if (left>=right)return;
// 找基准之前,找到中间大小的值
int midValIndex=findMidValIndex(array,left,right);
int tmp=array[left];
array[left]=array[midValIndex];
array[midValIndex]=tmp;
int pivot= partition(array,left,right);//基准
quick(array,left,pivot-1);
quick(array,pivot+1,right);
}
private int partition(int[] array,int start,int end){
int tmp=array[start];
while (start<end){
while (start<end && array[end]>=tmp){//=号必须有,防止死循环
end--;
}
//end下标遇到了小于tmp的值,然后将值放到start值
array[start]=array[end];
while (array[start]<=tmp&&start<end){
start++;
}
//start下标遇到>tmp的值,将值放到end
array[end]=array[start];
}
array[start]=tmp;
return start;
}
快速排序的递归实现
public void quickSort(int[] array){
quick(array,0,array.length-1);
}
private void quick(int[] array,int left,int right){
if (left>=right)return;
// 找基准之前,找到中间大小的值
int midValIndex=findMidValIndex(array,left,right);
int tmp=array[left];
array[left]=array[midValIndex];
array[midValIndex]=tmp;
int pivot= partition(array,left,right);//基准
quick(array,left,pivot-1);
quick(array,pivot+1,right);
}
private int findMidValIndex(int[] array,int left,int right){
//这个是用了三数取中法的具体实现, 若是不用三数取中法, 则可以直接将最左边的数作为基准
int mid=left+((right-left)>>>1);
if (array[left]<array[right]){
if (array[mid]<array[left])return left;
else if (array[mid]>array[right])return right;
else return mid;
}else {
if (array[mid]>array[left])return left;
else if (array[mid]<array[right])return right;
else return mid;
}
}
private int partition(int[] array,int start,int end){
int tmp=array[start];
while (start<end){
while (start<end && array[end]>=tmp){//=号必须有,防止死循环
end--;
}
//end下标遇到了小于tmp的值,然后将值放到start值
array[start]=array[end];
while (array[start]<=tmp&&start<end){
start++;
}
//start下标遇到>tmp的值,将值放到end
array[end]=array[start];
}
array[start]=tmp;
return start;
}
归并排序
思想 : 将数组不断二分, 直到分无可分. 然后每次将单个子序列安排到数组中使其有序,(有点类似于并查集,只不过并查集不是负责这方面的)
**归并排序
* 时间复杂度O(N*logN)
* 空间复杂度O(N)
* 稳定性:稳定
* 如果:array[s1]<=array[s2]不取等号那么就是不稳定的
* 学过的排序只有3个是稳定的:
* 冒泡 插入 归并
* @param array
*/
public void FmergeSort(int[] array){
// 归并排序的非递归方式
int gap=1;//每组的数据个数
while (gap<array.length){
//每次遍历需要确定归并的区间
for (int i = 0; i < array.length; i+=gap*2) {
int left=i;
int mid=left+gap-1;
if(mid>=array.length-1){
//防治越界
mid=array.length-1;
}
int right=mid+gap;
if (right>=array.length){
//防治越界
right=array.length-1;
}
merge(array,left,mid,right);
}
gap*=2;
}
}
public void mergeSort(int[] array){
mergeSortInternal(array,0,array.length-1);
}
private void mergeSortInternal(int[] array,int low,int high){
if (low>=high)return;
int mid=low+((high-low)>>>1);
mergeSortInternal(array,low,mid);
mergeSortInternal(array,mid+1,high);
merge(array,low,mid,high);//合并
}
private void merge(int[] array,int low,int mid,int high){
int[] tmp=new int[high-low+1];
int k=0;
int s1=low;
int e1=mid;
int s2=mid+1;
int e2=high;
while (s1<=e1 && s2<=e2) {
if (array[s1] <= array[s2]) {
tmp[k++] = array[s1++];
} else {
tmp[k++] = array[s2++];
}
}
while (s1<=e1){
tmp[k++]=array[s1++];
}
while (s2<=e2){
tmp[k++]=array[s2++];
}
//拷贝tmp数组的元素,放入原来的数组array当中
for (int i = 0; i < k; i++) {
array[i+low]=tmp[i];
}
}
计数排序
思想: 利用于哈希的思想 , 将数据直接定值在 ,数组中 , 然后从数组中依次取出即为有序
优点 : 快, 适用于范围集中的数据
时间复杂度 : O(N)
空间复杂度 :(最大值减最小值的范围)
稳定性 : 不稳定
/**
* 计数排序
* 时间复杂度O(N)
* 空间复杂度O(M)M:代表当前的数据的范围
* 稳定性:当前不稳定,但是能改的稳定
* @param array
*/
public void countingSort(int[] array){
int maxVal=array[0];
int minVal=array[0];
for (int i = 1; i <array.length ; i++) {
if (array[i]<minVal){
minVal=array[i];
}
if (array[i]>maxVal){
maxVal=array[i];
}
}
//已经找到最大值和最小值
int[] count=new int[maxVal-minVal+1];
for (int i = 0; i < array.length; i++) {
int index=array[i];
//不是i存放因为你的数组长度是最大值减最小值创建的
count[index-minVal]++;
}
//将计数数组中array数组每个数字出现的次数统计好了
int indexArray=0;
for (int i = 0; i < count.length; i++) {
while (count[i]>0){
// 因为不一定是i出现俩次
array[indexArray]=i+minVal;
count[i]--;//拷贝依次后数据就少一个
indexArray++;//原数组往后走
}
}
}