目录
1.冒泡排序
时间复杂度:O(n^2)。
空间复杂度:O(1)。
稳定性:稳定。
冒泡排序的思路是:通过从第一个元素开始交换,依次找到最大值,次大值,直到有序(针对升序,降序相反)。
代码(升序):
void BubbleSort(int* a, int n)
{
for (int j=0;j<n-1;j++)
{
int flag=1;
for (int i = 0; i < n - 1-j; i++)
{
if (a[i] > a[i + 1])
{
flag=0;
int tmp=a[i];
a[i]=a[i+1];
a[i+1]=tmp;
}
}
if(flag==1)
{
break;
}
}
}
第一个for循环代表总趟数,第二个for循环表示每一趟的具体比较,如果当前元素大于下一个元素则就交换,flag判断该趟是否有元素交换,如果没有,证明数组已有序,直接跳出循环。
注意点:
1.因为每次要比较当前元素与下一个元素,所以遍历总趟数要为n-1,这是图中j<n-1,i<n-1-j中n减去1的原因。
2.每一趟走完后,都会使一个数到达正确的位置,并且是从后向前的,因此在上一趟排完后,该趟遍历的数应该去掉最后一个,即上一趟到达正确位置的数,当j=0时,总遍历数为n-1,当j=1时,最后一个元素不必遍历,故总遍历数为n-1-1,当j=2时,最后两个元素不必遍历,故总遍历数为n-1-2,以此类推,每一趟的遍历数为n-1-j。
2.插入排序
时间复杂度:O(n^2)。
空间复杂度:O(1)。
稳定性:稳定。
插入排序的思想(升序):记录当前数并与前面的数比较,如果比前一个小那么前一个数往后移,否则就插入到当前比较数的后面。
代码:
void insertsort(int* a,int n)
{
for (int i=0;i<n-1;i++)
{
int end = i;
int key = a[end + 1];
while (end>=0)
{
if (a[end] > key)
{
a[end + 1] = a[end];
}
else
{
break;
}
end--;
}
a[end + 1] = key;
}
}
这里我们选取一个结尾end从0开始,end+1得到一个当前数key,从end开始往前与key比较,比key大的往后移,不然就在end+1处(即当前比较数的后面)填入key。
注意点:end可能取到-1(即key在已比较的数中最小),因此在while循环外填入key。(也可在里面填入,但这样必须对end=-1特殊处理)。
3.希尔排序
时间复杂度:O(nlogn)。
空间复杂度:O(1)。
稳定性:不稳定。
试想如果插入排序接近有序,那么每次只需比较几次就能达到有序,大大减少了比较次数。
希尔排序思路(升序):将这个数组每隔gap(gap逐渐减少,直到为1)个计为一组,进行插入排序,使小的数更快到前面,大的数更快到后面,使数组达到接近有序,最后进行一次(gap=1)插入排序。
代码:
void ShellSort(int* a, int n)
{
int gap = n;
while (gap>1)
{
gap /= 2;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
注意点:end的下一个为end+gap,因此i<n-gap,防止越界,比较也是间隔gap比较。
4.选择排序
时间复杂度:O(n^2)。
空间复杂度:O(1)。
稳定性:不稳定。
选择排序思路:每次从剩余数选一个最大或最小的数放到正确的位置,之后不再遍历这个位置,直到有序。
代码:
void SelectSort(int* a, int n)
{
int l = 0;
int r = n - 1;
while (l < r)
{
int begin = l;
int end = r;
int mini = l;
int maxi = l;
for (int i = l + 1; i <= r; i++)
{
if (a[i] < a[mini])
{
mini = i;
}
if (a[i] > a[maxi])
{
maxi = i;
}
}
Swap(&a[mini], &a[begin]);
if (maxi == begin)
{
maxi = mini;
}
Swap(&a[maxi], &a[end]);
l++;
r--;
}
}
为了减少遍历趟数,我们一次选出两个数,最大值与最小值放到开始与结尾的位置,下一次缩小范围,直到有序。
注意点:如果最大值在开始遍历的位置即begin,那么将最小值与begin位置的数交换时,最大值被换走,但maxi未更新,此时就无法使最大值达end位置,因此需要特殊判断一下,更新maxi的位置。
5.堆排序
时间复杂度:O(nlogn)。
空间复杂度:O(1)。
稳定性:不稳定。
堆排序思路:堆排序借助堆的特性,建堆,不断将堆顶的数与剩余数中最后一个数交换,再去掉最后一个数重新建堆,直至有序。
代码:
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void adjustdown(int* arr, int parent,int n)
{
int child = parent * 2 + 1;
while (child < n)
{
if (child+1<n&& arr[child + 1]<arr[child] )
{
child += 1;
}
if (arr[parent] > arr[child])
{
Swap(&arr[parent], &arr[child]);
parent = child;
child= parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, int n)
{
//建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
adjustdown(a, i, n);
}
int end = n - 1;
while (end)
{
Swap(&a[0], &a[end]);
end--;
adjustdown(a, 0, end);
}
}
建堆需要利用向下调整算法,从最后一个叶节点的父节点开始,依次往前进行向下调整,最后建出大堆,将堆顶的数与最后一个数交换,再将剩余数(去掉最大值)重新建堆,直至有序。
注意点:子节点child的父节点为parent=(child-1)/2,最后一个叶节点的父节点为(n-1-1)/2。
6.快速排序
时间复杂度:O(nlogn)。
空间复杂度:O(logn)。
稳定性:不稳定。
快速排序思路:从数组中选取一个数key使其到达正确的位置,如果key左边有序,右边有序,那么数组就有序。
代码:
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//三数取中
int get_mid(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[left] < right)
{
if (a[left] >a[mid])
{
return left;
}
else if(a[right]<a[mid])
{
return right;
}
else
{
return mid;
}
}
else
{
if (a[right] > a[mid])
{
return right;
}
else if (a[left] < a[mid])
{
return left;
}
else
{
return mid;
}
}
}
//使key到达正确位置
int PartSort1(int* a, int left, int right)
{
int keyi = left;
while (left < right)
{
while (left < right&&a[right] >= a[keyi])
{
right--;
}
while (left < right&&a[left] <= a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[keyi], &a[left]);
return left;
}
void QuickSort(int* a, int left, int right)
{
if(left>=right)
return ;
int mid = get_mid(a, left, right);
Swap(&a[left],& a[mid]);
int keyi=PartSort1(a, left, right);
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi+1, right);
}
如何使key到达正确位置呢?我们选取第一个数为key,用右指针找到比key小的,左指针找到比key大的,然后交换,最后左右指针相遇,将相遇点的数与key交换,key就到达正确的位置了,再递归key左右区间,使其有序。通过三数取中保证每次不是最坏情况(即key已是正确位置,如果数组已有序,没有三数取中快排的时间复杂度为(O(n^2)))。
注意点:
1.递归停止条件为left>=right,不是left==right,因为如果key在最右边时,key的右区间为[key+1,key],此时left>right。
2.在使key到达正确位置的过程中,left与right可能越界(即key已在正确位置),因此需要再内部循环加上left<rught。
7.归并排序
时间复杂度:O(nlogn)。
空间复杂度:O(n)。
稳定性:稳定。
归并排序思路:借用将两个有序数组合并成一个有序数组的思路,将一个数组分为左右两部分,如果左边有序,右边也有序,即可借助另一个数组将这两部分合并为完整的有序数组。
代码:
void partsort(int* str, int* a, int left,int right)
{
if (left == right)
{
return;
}
int mid = (left + right) / 2;
partsort(str, a, left, mid);
partsort(str, a, mid+1, right);
int begin1 = left, end1 = mid;
int begin2 = mid+1, end2 = right;
int c = left;
while (begin1<=end1&&begin2<=end2)
{
if(a[begin1] <= a[begin2])
{
str[c++] = a[begin1++];
}
else
{
str[c++] = a[begin2++];
}
}
while (begin1<=end1)
{
str[c++] = a[begin1++];
}
while (begin2<=end2)
{
str[c++] = a[begin2++];
}
memcpy(a+left, str+left, sizeof(int) * (right - left + 1));
}
void MergeSort(int* a, int n)
{
int* str = (int*)malloc(sizeof(int) * n);
partsort(str, a, 0,n-1);
}
先递归左右区间使其有序,然后开始对比,将数插进临时数组,最后在拷贝回去。
注意点:在某个区间进行排序时,无论时插数进临时数组还是拷贝回去,记得要加上区间起始值left,保证区间的数仍在该区间内。