本人第一次写技术博客,因为是感觉写在笔记本上找的时候又麻烦,而且写字真的很费劲。所以想想还是终于下定决心写下博客,希望大牛们可以多个点意见,小弟不胜感激,轻点喷,我也是没学多久,谢谢各位大牛们。
话不多说,进入正题,排序算法有很多,为了选择合适的算法,可以按照以下标准作参考:
(1)时间复杂度(2)空间复杂度
(3)编程工作
时间复杂度:主要分析关键字的比较次数和记录的移动次数
空间复杂度:分析算法中需要多少辅助内存
稳定性:在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。(下面会用到这个概念)
主要排序法有:
一、冒泡(Bubble)排序
二、选择排序
三、插入排序
四、壳(Shell)排序——缩小增量
五、归并排序
六、快速排序
七、堆排序
八、基数排序
以下都是从小到大排序:
一、冒泡(Bubble)排序
void BubbleSort()
{
for(int i=1;i<n;i++)
{
for(int j=0;j<n-i;j++)
{
if(a[j]>a[j+1])//比较交换相邻元素
{
int temp;
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
}
}
很简单的交换排序,时间复杂度O(n2),冒泡排序法是一种稳定的排序,需要的辅助空间是O(1),这种排序法是当n小的时候,效率高。
二、选择排序
void SelectSortArray()
{
int min_index;
for(int i=0;i<n-1;i++)
{
min_index=i;
for(int j=i+1;j<n;j++)//每次扫描选择最小项
if(arr[j]<arr[min_index]) min_index=j;
if(min_index!=i)//找到最小项交换,即将这一项移到列表中的正确位置
{
int temp;
temp=arr[i];
arr[i]=arr[min_index];
arr[min_index]=temp;
}
}
}
每次扫描最小(大)项,排在相应的位置,比较简单,时间复杂度是O(n2),空间复杂度是O(1),是不稳定的排序。
三、直接插入排序
void InsertSort()
{
for(int i=1;i<n;i++)//循环从第二个数组元素开始,因为arr[0]作为最初已排序部分
{
int temp=arr[i];//temp标记为未排序第一个元素
int j=i-1;
while (j>=0 && arr[j]>temp)/*将temp与已排序元素从小到大比较,寻找temp应插入的位置*/
{
arr[j+1]=arr[j];
j--;
}
arr[j+1]=temp;
}
}
直接插入排序:将待排序的元素插入到先前的有序序列中。最佳效率O(n);最糟O(n2),也要考虑移动的次数,故时间复杂度为O(n2)。空间复杂度为O(1),并且排序是稳定的。当数据有一定顺序的情况下,插入排序效率较好,如果无序则和冒泡,选择一样。
四、壳(Shell)排序
void ShellSort()
{
for(int h=3;h<0;h--)//增量递减,以增量3,2,1为例
{
for(int L=0;L<(n-1)/h;L++)//重复分成的每个子列表
{
for(int i=L+h;i<n;i+=h)//对每个子列表应用插入排序
{
int temp=arr[i];
int j=i-h;
while(j>=0&&arr[j]>temp)
{
arr[j+incr]=arr[j];
j-=h;
}
arr[j+h]=temp;
}
}
}
}
shell排序属于插入排序,对直接插入进行了改进,其实就是引入了增量(增量一般计算公式为h = (n-1)/3),可以是数据大跨度的移动,减小了比较次数。最差为O(ns)(1<s<2)最优为O(log2n),空间复杂度为O(1),也是不稳定排序。
五、归并排序
void MergeSort(int low,int high)
{
if(low>=high)
return;//每个子列表中剩下一个元素时停止
else
int mid=(low+high)/2;/*将列表划分成相等的两个子列表,若有奇数个元素,则在左边子列表大于右侧子列表*/
MergeSort(low,mid);//子列表进一步划分
MergeSort(mid+1,high);
int [] B=new int [high-low+1];//新建一个数组,用于存放归并的元素
for(int i=low,j=mid+1,k=low;i<=mid && j<=high;k++)/*两个子列表进行排序归并,直到两个子列表中的一个结束*/
{
if (arr[i]<=arr[j];)
{
B[k]=arr[i];
i++;
}
else
{ B[k]=arr[j];
j++;
}
}
for( ;j<=high;j++,k++)//如果第二个子列表中仍然有元素,则追加到新列表
B[k]=arr[j];
for( ;i<=mid;i++,k++)//如果在第一个子列表中仍然有元素,则追加到新列表中
B[k]=arr[i];
for(int z=0;z<high-low+1;z++)//将排序的数组B的 所有元素复制到原始数组arr中
arr[z]=B[z];
}
归并排序:即将数据分成若干个子列,子列是有序的,然后再把有序子列合并。归并排序是稳定的,数组需要的额外空间为O(n),链表需要的额外空间为O(log2n),时间复杂度为O(nlog2n).归并的最佳、平均和最糟用例效率之间没有差异。 适用于排序大列表,基于分治法。
O(nlog(n))
int Partition(int [] arr,int low,int high)
{
int pivot=arr[low];//采用子序列的第一个元素作为枢纽元素
while (low < high)
{
//从后往前栽后半部分中寻找第一个小于枢纽元素的元素
while (low < high && arr[high] >= pivot)
{
--high;
}
//将这个比枢纽元素小的元素交换到前半部分
swap(arr[low], arr[high]);
//从前往后在前半部分中寻找第一个大于枢纽元素的元素
while (low <high &&arr [low ]<=pivot )
{
++low ;
}
swap (arr [low ],arr [high ]);//将这个枢纽元素大的元素交换到后半部分
}
return low ;//返回枢纽元素所在的位置
}
void QuickSort(int [] a,int low,int high)
{
if (low <high )
{
int n=Partition (a ,low ,high );
QuickSort (a ,low ,n );
QuickSort (a ,n +1,high );
}
}
快速排序法:从待排的数据序列中任取一个数据作为分界值,小的在左,大的在右,进行排序。最好情况(每次选到中间值)时间复杂度为 O(nlog2n),最坏情况(每次总是选到最小或最大)O(n2),平均效率O(nlog2n),辅助空间O(logn)(每次都要分给一个额外空间,而总共有logn次), 适用于大的排列表
int i = s;
int x = num[s];
for (int j = 2 * i; j <= t; j = 2 * j) {
if (j < t && num[j] < num[j + 1])
j = j + 1;// 找出较大者把较大者给num[i]
if (x > num[j])
break;
num[i] = num[j];
i = j;
}
num[i] = x;
}
void heapsort(int[] num, int n) {
// 初始建堆从n/2开始向根调整
int i;
for (i = n / 2; i >= 1; i--) {
resetHeap(num, i, n);//初始堆过程
}
for (i = n; i > 1; i--) {
num[0] = num[i];// 将堆顶元素与第n,n-1,.....2个元素相交换
num[i] = num[1];
num[1] = num[0];// 从num[1]到num[i-1]调整成新堆
resetHeap(num, 1, i - 1);
}
}
堆排序是指利用堆积术这种结构所设计的一种排序算法,可以利用数组的特点快速定位指定索引的元素。堆排序是不稳定的排序方法,空间复杂度为O(1),最坏时间复杂度为O(nlog2n),堆排序的平均性能接近于最坏性能,主要是建最大堆和反复建堆这两部分开销组成。建最大堆的时间为O(n),反复建堆的时间为O(log2n)。
八、基数排序
int[] array = {3,2,9,2,5,322,1566,1178,78,990,12,432,56};
radixSort(array,10,4);
void radixSort(int[] array,int radix, int distance) {
//array为待排序数组
//radix,代表基数
//代表排序元素的位数
int length = array.length;
int[] copy= new int[length];//用于暂存元素
int[] count = new int[radix];//用于计数排序
int divide = 1;
for (int i = 0; i < distance; i++) {
System.arraycopy(array, 0,copy, 0, length);
Arrays.fill(count, 0);
for (int j = 0; j < length; j++) {
int tempKey = (copy[j]/divide)%radix;
count[tempKey]++;
}
for (int j = 1; j < radix; j++) {
count [j] = count[j] + count[j-1];
}
for (int j = length - 1; j >= 0; j--) {
int saveKey = (copy[j]/divide)%radix; 、
count[saveKey]--;
array[count[saveKey]] = copy[j];
}
divide = divide * radix;
}
基数排序思路是将待排序数据拆分成多个关键字进行排序。(基数排序对任一子关键字进行排序时,必须借助另一种排序算法,而且算法必须是稳定的)。假定,待排序咧为n,k个关键值,关键值的取值范围为m,则时间复杂度为O(k(n+m)),其中一趟分配的时间复杂度O(n),一趟收集的时间复杂度为O(n),空间复杂度:O(mk+n),算法是稳定的。