目录
前言
本篇文章记录数据结构内部排序的所有方法以及其核心思想加代码。
一、插入排序
插入排序在数据结构学习中,一般有三种排序,分别是直接插入排序,折半插入排序,以及希尔排序。
1.直接插入排序
核心思想:每次将一个要排序的记录按关键字大小插入前面已经排好序的子序列。
空间复杂度:O(1)
时间复杂度:O(n^2)
稳定性:稳定
/**
*以从小到大为例子
* @param a 待排数组
* @param n 数据长度
*/
public static void InsertSort(int a[],int n){
int i,j,temp;
//从a[1]开始插入,第一个元素必然是排好序的,可以不用管。
for(i=1;i<n;i++){
//当插入数据小于前面数据时,前面的位置后移,留出前面合适位置给他
if(a[i]<a[i-1]){
temp=a[i];
for(j=i-1;j>=0&&a[j]>temp;j--){
a[j+1]=a[j];
}
a[j+1]=temp;
}
}
for (int k = 0; k < a.length; k++) {
System.out.print(a[k]+" ");
}
System.out.println();
}
2.折半插入排序
核心思想:基于直接插入排序,每次通过折半查找找到要插入的位置,并将插入位置的元素后移。
时间复杂度:O(n^2),但比较次数减少
空间复杂度:O(1)
稳定性:稳定
/**
* 折半插入排序(以小到大为例)
* @param a 待排数组
* @param n 待排元素个数
*/
public static void InsertSortByMid(int a[],int n){
int i,j,low,high,mid,temp;
for(i=1;i<=n;i++){
temp=a[i];
low=0;
high=i-1;
while(low<=high){
//通过中间值不断将其划分,直到找到插入位置
mid=(low+high)/2;
if(temp>=a[mid]){
low=mid+1;
}else{
high=mid-1;
}
}
//插入位置后面元素后移
for(j=i-1;j>temp&j>=low;j--){
a[j+1]=a[j];
}
a[low]=temp;
}
for (int k = 0; k < a.length; k++) {
System.out.print(a[k]+" ");
}
}
3.希尔排序:
核心思想:也是基于插入排序,将待排列表分割成若干个形如L[i,i+d,i+2d,....,i+kd]的特殊子表,即把相隔d的元素组成个子表,对各个子表进行排序,当整个子表的元素有序时,再对全体记录进行一次插入排序排序。
时间复杂度:未知
空间复杂度:O(1)
稳定性:不稳定
/**
* 希尔排序(以小到大)
* @param a 待排数组
* @param n 数据长度
*/
public static void ShellSort(int a[],int n){
int dk,i,j,temp;
//dk为增量,定义增量变化
for(dk=n/2;dk>=0;dk=dk/2){
for(i=dk+1;i<=n;i++){
if(a[i]<a[i-dk]){
temp=a[i];
//找到插入位置,并将元素后移.
for(j=i-dk;j>0&&temp<a[j];j-=dk){
a[j+dk]=a[j];
}
a[j+dk]=temp;
}
}
}
}
二 、交换排序
1.冒泡排序
核心思想:从后往前(从前往后)两两比较相邻元素的值,若为逆序(a[i-1]>a[i]),则交换他们,直到序列比较完。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
/**
* 冒泡排序(以小到大)
* @param a 待排数组
* @param n 数据长度
*/
public static void BubbleSet(int a[],int n){
//标记
boolean flag=false;
int temp;
for(int i=0;i<n-1;i++){
//从后开始,不断往前冒泡
for(int j=n-1;j>i;j--){
if(a[j]<a[j-1]){
temp=a[j];
a[j]=a[j-1];
a[j-1]=temp;
//每次发生交换将标记改为true
flag=true;
}
}
//如果某一次的冒泡没有发生交换,则可以认为数组已经有序,直接终止
if(flag==false){
for (int k = 0; k < n; k++) {
System.out.print(a[k]+" ");
}
System.out.println();
return;
}
}
for (int i = 0; i < n; i++) {
System.out.print(a[i]+" ");
}
System.out.println();
}
2.快速排序
核心思想:基于分治法,每次取待排元素中的一个为基准(通常为首元素),通过一趟排序将待排元素表分为两部分,一部分是小于基准元素,一部分是大于基准元素,这个过程为一次划分,即确定一个元素在排序后的最终位置上。随后不断递归这两部分。
时间复杂度:O(nlog2n)
空间复杂度:O(log2n)
稳定性:不稳定
/**
* 通过基准元素的划分
* @param a 待排数组
* @param low 数组起始
* @param high 数组末尾
* @return
*/
public static int Partition(int a[],int low,int high){
int pivot=a[low]; //确定基准元素
while(low<high){
//确定好基准元素后,低部分留出空位,因此先从高部分开始,找到小于基准元素的位置
while(low<high&&a[high]>=pivot) high--;
//将小于基准元素的位置交换到低部分的开始空位中
a[low]=a[high];
//从低部分开始找到大于基准元素的位置
while(low<high&&a[low]<=pivot) low++;
//将大于基准元素的位置交换到高部分的空位中
a[high]=a[low];
}
//将最终基准元素要去的位置赋值基准元素
a[low]=pivot;
return low;
}
/**
* 快速排序
* @param a 待排数组
* @param low 数据起始
* @param high 数据末尾
*/
public static void QuickSort(int a[],int low,int high){
if(low<high){
int partition = Partition(a, low, high);
//不断递归划分
Partition(a,low,partition-1);
Partition(a,partition+1,high);
}
}
三、选择排序
1.简单选择排序
核心思想:每一趟在n-i+1(i=1,2,3,...,n)个待排元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1躺做完。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
/**
* 冒泡排序(小到大)
* @param a 待排数组
* @param n 待排元素个数
*/
public static void SelectSort(int a[],int n){
int temp;
for(int i=0;i<n-1;i++){
int min=i;
for(int j=i+1;j<n;j++){
if(a[j]<a[min])
min=j;
}
if(min!=i){
temp=a[i];
a[i]=a[min];
a[min]=temp;
}
}
}
2.堆排序
核心思想:将待排元素看成一个顺序存储的完全二叉树,以结点i为例,2i、2i+1为其左右孩子。若i>n/2(向上取整),则i为叶子节点。此时若结点i(非叶子结点)的关键字大于其左右孩子的关键字,那么,其就为大根堆;若小于左右结点关键字,则其为小根堆。由大根堆构成的完全二叉树的根节点此时即为最大的元素,小根堆的完全二叉树的根结点即为最小的元素。此时将待排元素构建成堆后,输出堆顶元素即根结点,并将堆底元素送入堆顶元素,若此时不满足堆元素的定义,则将堆顶元素向下调整继续保持堆的元素的性质,再继续输出堆顶元素。
时间复杂度:O(nlog2n)
空间复杂度:O(1)
稳定性:不稳定
/**
* 堆排序(大根堆)
* @param a 待排数组
* @param length 数据长度
*/
public static void HeapSort(int a[],int length){
int temp;
//先构建大根堆
BuildMaxHeap(a,length);
//不断输出堆顶元素
for(int i=length;i>1;i--){
temp=a[i];
a[i]=a[1];
a[1]=temp;
//不断根据堆顶元素调整堆
HeadAdjust(a,1,i-1);
}
}
/**
* 调整堆
* @param a 待排数组
* @param k 待排子树的根结点元素的位置
* @param length 待排数组长度
*/
public static void HeadAdjust(int a[],int k,int length){
//通过a[0]记录待排元素,也就是把待排元素的a[0]位置充当辅助
a[0]=a[k];
//找到根结点k元素的孩子,即2k,2k+1
for(int i=2*k;i<=length;i*=2){
//比较左右元素大小,记录大元素的位置
if(i<length&&a[i]<=a[i+1]){
i++;
}
//比较根节点和左右孩子中最大的元素,若大于,满足大根堆,直接退出
if(a[0]>a[i])
break;
//根节点小于左右孩子中最大的元素
else{
//将最大的元素赋值给根元素
a[k]=a[i];
//继续判断,若最大元素赋值给当前根元素之后,其最大子元素的左右还在与替换后的根结点的关系
k=i;
}
}
//找到根元素适合插入的位置
a[k]=a[0];
}
/**
* 构建堆(以大根堆为例)
* @param a 待排元素
* @param length 待排元素长度
*/
public static void BuildMaxHeap(int a[],int length){
for(int i=length/2;i>0;i--){
//构建堆要从非叶子结点开始,也就是i<n/2时
HeadAdjust(a,i,length);
}
}
四、归并排序
1.2路归并排序
核心思想:排序表有n个记录,则可将其视为n个有序的子表,每个子表长度为一1,然后两两合并,得到n/2个长度为2的或1的有序表;继续两两归并,不断重复,直到合并成一个长度为n的有序表。
时间复杂度:O(nlong2n)
空间复杂度:O(n)
稳定性:稳定。
//辅助数组
public static int b[]=new int[9];
/**
* 合并排序
* @param a 待排元素
* @param low 起始位置
* @param mid 中间位置
* @param high 末尾位置
*/
public static void Merge(int a[],int low,int mid,int high){
int i,j,k;
//将待排元素赋值给赋值数组
for(k=low;k<=high;k++){
b[k]=a[k];
}
//对辅助数组进行划分为[low,mid],[mid+1,hign]
for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++){
//不断比较[low,mid],[mid+1,hign]中的元素,取出其最小者
if(b[i]<=b[j]){
a[k]=b[i++];
}else{
a[k]=b[j++];
}
}
//当[low,mid],[mid+1,hign]中有一方结束时,针对另一方可能有多个元素的剩余时,不断继续进行赋值
while(i<=mid) a[k++]=b[i++];
while(j<=high) a[k++]=b[j++];
}
/**
* 2路合并排序
* @param a 排序数组
* @param low 起始位置
* @param high 末尾位置
*/
public static void MergeSort(int a[],int low,int high){
if(low<high){
//不断对待排元素进行划分,直到元素个数为1
int mid=(low+high)/2;
MergeSort(a,low,mid);
MergeSort(a,mid+1,high);
//划分完之后不断合并排序
Merge(a,low,mid,high);
}
}