排序的稳定性:假设在待排序的序列中存在多个相同关键字的记录,经过排序,这些关键字的位置保持不变,则称这个排序算法是稳定的,否则是不稳定的。
1. 选择排序(不稳定)
选择排序是一种直观的排序算法,每次找到最大的或者最小的数与,存放在序列的起始元素,知道所有的元素都排序结束。选择排序是不稳定的,假设5,5,3进行排序,3比第一个5小交换位置,导致第一个5摞到第二个5后面,所以选择排序是不稳定的排序。
以下面5个无序的数据为例:
56 12 80 91 20
排序过程:
a.12 56 80 91 20
b.12 20 80 91 56
c.12 20 56 91 80
d.12 20 56 80 91
代码实现:
void select_sort(int *arr,int len)
{
if( arr == NULL || len < 0)
{
return ;
}
int i = 0;
for( i = 0;i < len - 1;i++)
{
int min = arr[i];
int min_index = i;
for( int j = i + 1;j < len;j++)
{
if( arr[j] < min)
{
min = arr[j];
min_index = j;
}
}
swap(&arr[i],&arr[min_index]);
}
}
2.简单交换排序(稳定排序)
根据序列中两个关键字的比较结果来对换在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
以下面5个无序的数据为例:
8 2 1 4 9 5 7 3 6
a.(只细讲第一趟)
2 8 1 4 9 5 7 3 6
1 2 8 4 9 5 7 3 6(1是序列中最小的)
void exchange_sort(int *arr,int len)
{
if( arr == NULL || len < 0)
{
return ;
}
int i = 0;
for( i = 0; i < len - 1;i++)
{
for( int j = i + 1;j < len ;j++)
{
if( arr[i] > arr[j])
{
swap(&arr[j],&arr[i]);
}
}
}
}
3.冒泡排序(稳定排序)
冒泡排序是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换。
以下面的数字为例子:
6 2 4 1 5 9
a. 2 6 4 1 5 9
2 4 6 1 5 9
2 4 1 6 5 9
2 4 1 5 6 9
2 4 1 5 6 9
b. 2 1 4 5 6 9
c. 1 2 4 5 6 9
void bubble_sort(int *arr,int len)
{
if( arr == NULL || len < 0)
{
return ;
}
for( int i = 0;i < len - 1;i++)
{
for( int j = 0;j < len - 1 - i;j++)
{
if( arr[j] > arr[j+1])
{
swap(&arr[j],&arr[j+1]);
}
}
}
}
假设一组数后面的数是排好序的,则代码需要进行优化
bool bubble_sort_ex(int *arr,int len)
{
if( arr == NULL || len < 0)
{
return false;
}
for( int i = 0;i < len - 1;i++)
{
bool flag = true;
for( int j = 0;j < len - 1 - i;j++)
{
if( arr[j] > arr[j+1])
{
swap(&arr[j],&arr[j+1]);
flag = false;
}
}
if( flag )
{
break;
}
}
return false;
}
4.插入排序(稳定排序)
有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序。
以下列数字为例:
65 27 59 64 58
刚开始时把65认为已经排好序
a.插入27
27 65 59 64 58
b.插入59
27 59 65 64 58
c.插入64
27 59 64 65 58
d.插入58
27 58 59 64 65
void insert_sort(int *arr,int len)
{
if( arr == NULL || len < 0)
{
return ;
}
int i = 0;
int j = 0;
for(i = 1;i < len;i++)
{
int temp = arr[i];
for( j = i - 1; j >= 0;j--)
{
if( arr[j] > temp)
{
arr[j+1] = arr[j];
}
else
{
break;
}
}
arr[j+1] = temp;
}
}
5.希尔排序(不稳定排序)
希尔排序属于插入排序的一种,也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。把记录按步长 gap 分组,对每组记录采用直接插入排序方法进行排序。随着步长逐渐减小,所分成的组包含的记录越来越多,当步长的值减小到 1 时,整个数据合成为一组,构成一组有序记录,则完成排序。
排序前: 9 1 2 5 7 4 8 6 3 5
gap = 5: 4 1 2 3 5 9 8 6 5 7
gap = 2: 2 1 4 3 5 6 5 7 8 9
gap = 1: 1 2 3 4 5 5 6 7 8 9
排序后: 1 2 3 4 5 5 6 7 8 9
void shell(int *arr,int len, int gap)
{
int i;
int j;
// // 把距离为 gap 的元素编为一个组,扫描所有组
for (i =gap; i<len; i++)
{
int tmp = arr[i];
for (j=i-gap; j>=0 && arr[j] > tmp; j -= gap) 对距离为 gap 的元素组进行排序
{
arr[j+gap] = arr[j];
}
arr[j+gap] = tmp;
}
}
void shell_sort(int *arr,int len)
{
for (int i=len/1000 ;i>1; i=i/2) //计算gap
{
shell(arr, len ,i);
}
shell(arr, len ,1);
}
6.归并排序(稳定排序)
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将待排序序列R[0…n-1]看成是n个长度为1的有序序列,将相邻的有序表成对归并,得到n/2个长度为2的有序表;将这些有序序列再次归并,得到n/4个长度为4的有序序列;如此反复进行下去,最后得到一个长度为n的有序序列。
static int tmp[20] = {0}
// 把两个有序的数组排序成一个数组
void merge(int *arr, int start, int middle, int end)
{
int first = start;
int second = middle + 1;
int index = start;
while ((first <= middle) && (second <= end)){
if (arr[first] >= arr[second])
tmp[index++] = arr[second++];
else
tmp[index++] = arr[first++];
}
while(first <= middle) tmp[index++] = arr[first++];
while(second <= end) tmp[index++] = arr[second++];
for (first = start; first <= end; first++)
{
arr[first] = tmp[first];
}
}
// 递归划分数组
void merge_Sort(int *array, int start, int end)
{
if (start >= end)
return;
int middle = ((end + start) >> 1);
merge_Sort(array, start, middle);// 递归划分左边的数组
merge_Sort(array, middle+1, end);// 递归划分右边的数组
merge(array, start, middle, end);// 对有序的两个数组进行合并成一个有序的数组
}
7.堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。
void heap_adjust(int *arr, int start, int end)
{
int i;
int tmp = arr[start];
for (i=2*start+1; i<=end; i=2*i+1)
{
if (i+1<=end && arr[i] < arr[i+1])
{
i++;
}
if (tmp < arr[i])
{
arr[start] = arr[i];
start = i;
}
else
{
break;
}
}
arr[start] = tmp;
}
char *heap_sort(int *arr, int len) //3/2N * Log2N
{
//1.从下到上,建立大根堆
for (int i=(len-2)/2; i>=0; i--)//N/2*log2N
{
heap_adjust(arr,i,len-1);
}
//2.交换
swap(&arr[0], &arr[len-1]);
//3.调整为大根堆
for (int i=len-2; i>0 ;i--) //N*log2N
{
heap_adjust(arr,0,i);
swap(&arr[0], &arr[i]);
}
return __FUNCTION__;
}
8.快速排序
快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
void partition(int *arr,int L, int R)
{
if (L >= R)
{
return ;
}
int left = L;
int right = R;
int tmp = arr[L];
while(L<R)
{
while(tmp <= arr[R] && L<R)
{
R--;
}
arr[L] = arr[R];
while(arr[L] <= tmp && L<R)
{
L++;
}
arr[R] = arr[L];
}
arr[L] = tmp;
partition(arr,left,L-1);
partition(arr,L+1,right);
}
void quick_sort(int *arr, int len)
{
partition(arr, 0, len-1);
}
对这8种常用排序的时间复杂度和空间复杂度分析