1. 简单选择排序。
排序原理:每次选择数组中最小的元素放在数组开头,多次循环完成排序。
例如:数组 [3,7,2,9,3,1,0,6] 的排序过程:
[3 7 2 9 3 1 0 6]
[3 7 2 9 3 1 0 6](寻找最小元素)
[0 7 2 9 3 1 3 6](交换)
0 [7 2 9 3 1 3 6](寻找最小元素)
0 [1 2 9 3 7 3 6](交换)
… …
0 1 2 3 3 6 7 9
程序如下:
#include <stdio.h>
#define INF 0xffff
//输出数组
void outputArray(int test[], int length)
{
int i;
for(i = 0; i < length; i++)
printf("%d ", test[i]);
printf("\n");
}
//交换两个数
void swap_num(int &a,int &b)
{
int temp = b;
b = a;
a = temp;
}
//简单选择排序
void simpleSort(int test[], int length)
{
int i,j;
for(i = 0; i < length - 1; i++)
{
int min_j = i + 1;
for(j = i + 1; j < length; j++)
{
if(test[j] < test[min_j])
min_j = j;
}
if(test[i]>test[min_j])
swap_num(test[i],test[min_j]);
}
}
int main()
{
int test[] = {3,7,2,9,3,1,0,6};
int length = sizeof(test) / sizeof(int);
printf("排序前:");
outputArray(test, length);
printf("排序后:");
simpleSort(test, length);
outputArray(test, length);
return 0;
}
复杂度分析:
由于使用了2个for循环,时间复杂度为O();排序的时候是在数组内部进行,空间复杂度为O(1)。由于是交换排序,所以排序过程是不稳定的。
2. 冒泡排序。
排序原理:依次比较两个相邻的元素,如果顺序不对就交换。重复以上步骤,直到没有可以交换的元素为止。
例如:数组 [3,7,2,9,3,1,0,6] 的排序过程:
[3 7 2 9 3 1 0 6]
[3 2 7 9 3 1 0 6]
[3 2 7 9 3 1 0 6]
[3 2 7 3 9 1 0 6]
[3 2 7 3 1 9 0 6]
[3 2 7 3 1 0 9 6]
[3 2 7 3 1 0 6 9](第一轮排序完成,第一趟排序确定了最大的数为9)
[2 3 3 1 0 6 7 9](第二轮排序完成,第二趟排序确定了最二大的数为7)
[2 3 1 0 3 6 7 9](第三轮排序完成,第三趟排序确定了最三大的数为6)
[2 1 0 3 3 6 7 9](第四轮排序完成)
[1 0 2 3 3 6 7 9](第五轮排序完成)
[0 1 2 3 3 6 7 9](第六轮排序完成)
程序如下:
flag变量表示是否存在可以交换的元素;
//冒泡排序
void bubbleSort(int test[], int length)
{
int flag = 1;
while(flag){
flag = 0;
for(int i = 0; i < length-1; i++){
if(test[i] > test[i+1]){
flag = 1;
swap_num(test[i], test[i+1]);
}
}
//outputArray(test, length);
}
}
// Java
public void bubbleSort(int[] arr) {
for(int i = arr.length - 1; i >= 0; i--) { // 经历arr.length-1趟排序
for(int j = 0; j < i; j++) {
if(arr[j] > arr[j + 1])
swap(arr, j, j + 1);
}
}
}
private void swap(int[] arr, int i, int j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
复杂度分析:
两层循环,时间复杂度O(),空间复杂度O(1),由于冒泡排序始终保证小元素在大元素前面,所以是稳定的排序。
3. 插入排序。
排序原理:创建一个空数组,遍历排序数组的每个元素,使其插入到创建数组的合理位
置,完成排序。
例如:数组[3,7,2,9,3,1,0,6] 的排序过程:
[3] [7 2 9 3 1 0 6]
[3 7] [2 9 3 1 0 6]
[2 3 7] [9 3 1 0 6]
… …
[0 1 2 3 3 6 7 9]
插入的详细过程如下:
[1 5 8 9] [4]
首先让4和9比较,如果4<9,两者交换位置;4再和8比较,两者交换位置;4再和5比较,两者交换位置;4和1比较,4>1,跳出循环,插入过程结束。
程序如下:
void insertSort(int test[],int length)
{
int i, j, num;
for(i = 1; i < length; i++){ // 从test[1]开始执行插入操作
//寻找插入的位置的同时,让数组元素右移
num = test[i]; //记录要插入的值
j = i - 1;
while(num < test[j] && j >= 0){
test[j + 1] = test[j];
j--;
}
test[j + 1] = num;
}
}
4. 希尔排序。
排序原理:希尔排序是基于插入排序的方法;通过增量delta将序列分成几个小序列,通过插入排序使小序列有序;当delta=1时,完成排序后即完成了最终排序。
例如:数组[3,7,2,9,3,1,0,6] 的排序过程:
当delta=4时,数组分为4组,如下所示:(3,3)(7,1)(2,0)(9,6)
每一组进行插入排序,得到结果:[3 1 0 6 3 7 2 9]
当delta=2时,数据分为2组,如下所示:(3,0,3,2)(1,6,7,9)
每一组进行插入排序,得到结果: [0 1 2 6 3 7 3 9]
当delta=1时,数据只有一组,完成插入排序,结果为:[0 1 2 3 3 6 7 9]
程序如下:
void insertSort(int test[], int length, int delta)
{
int i, j, k, num;
for(k = 0; k < delta; k++){ //有delta个子序列, k表示第k个子序列
for(i = k + delta; i < length; i += delta){ // 对第k个自序列进行插入排序
j = i - delta;
num = test[i];
while(num < test[j] && j >= 0){
test[j + delta] = test[j];
j -= delta;
}
test[j + delta]=num;
}
}
}
void shellSort(int test[], int length, int delta[], int delta_length)
{
for(int i = 0; i < delta_length; i++)
insertSort(test, length, delta[i]);
}
int main()
{
int test[]={3,7,2,9,3,1,0,6};
//设置增量数组
int delta[]={4,2,1};
int length=sizeof(test)/sizeof(int);
int delta_length=sizeof(delta)/sizeof(int);
printf("排序前:");
outputArray(test,length);
printf("排序后:");
shellSort(test,length,delta,delta_length);
return 0;
}
一般认为希尔排序的时间复杂度为O(n^1.5),适用于中规模序列排序。希尔排序是不稳定的排序。
5. 归并排序。
排序原理:运用了分治法的思想,把序列平均分成两段,把两段分别进行排序,再把排好的两段合在一起。对于子段排序时,递归调用上面的过程。
例如:数组[3,7,2,9,3,1,0,6] 的排序过程:
[3 7 2 9] [3 1 0 6] 分组
[3 7] [2 9] [3 1] [0 6] 递归分组
[3 7] [2 9] [1 3] [0 6] 排序
[2 3 7 9] [0 1 3 6] 合并
[0 1 2 3 3 6 7 9] 再合并,得到排序结果
合并的原理(双指针):设置两个指向子序列开始位置的指针,再建立一个空数组。让两个指针指向的值进行比较,较小的值进入数组,指向较小数的指针加1,指向下个位置。重复以上步骤,直到所有元素进入数组。
程序如下:
//将test数组的一部分进行归并排序
void mergeSort(int test[], int beginIndex, int endIndex, int length)
{
int LL = endIndex - beginIndex + 1;
int mid = (beginIndex + endIndex) / 2;
if(LL <= 2) { // 递归结束条件
if(test[beginIndex] > test[endIndex]){
int TT = test[beginIndex];
test[beginIndex] = test[endIndex];
test[endIndex] = TT;
}
}
else{
mergeSort(test, beginIndex, mid, length);
mergeSort(test, mid+1, endIndex, length);
//合并的过程
int i = beginIndex;
int j = mid + 1;
int k = 0;
int* temp = new int[LL]; // 新建一个数组
while(i <= mid && j <= endIndex){
if(test[i] <= test[j]){
temp[k] = test[i];
k++;
i++;
}
else{
temp[k]=test[j];
k++;
j++;
}
}
while(i<=mid){
temp[k]=test[i];
k++;
i++;
}
while(j<=endIndex){
temp[k]=test[j];
k++;
j++;
}
//将temp写回test
int ii,jj;
for(ii = beginIndex, jj = 0; ii <= endIndex; ii++, jj++){
test[ii] = temp[jj];
}
}
}
6. 快速排序。
排序原理:选择一个初始元素a,利用算法将数组分为两段:比a小,比a大。
将比a小的元素放在a前面,比a大的元素放在a的后面。再使用递归对前后两段进行如下操作,当每段元素只有2个或3个时,就停止上面操作,排序完成。
分组原理:假设序列为r[left],r[left+1],… … ,r[right],设置两个指针i和j,指向left和right。
x=r[left];反复执行以下操作使得i,j相遇。
①j向左扫描,直到r[j]<x,r[i]=r[j],r[j]置为空;
②i向右扫描,直到r[i]>x,r[j]=r[i],r[i]置为空。
例如:数组[3,7,2,9,3,1,0,6] 的分组排序过程:
[(3) 7 2 9 3 1 0 6]
[* 7 2 9 3 1 0 6]
[0 7 2 9 3 1 * 6]
[0 7 2 9 3 1 * 6]
[0 * 2 9 3 1 7 6]
[0 * 2 9 3 1 7 6]
[0 1 2 9 3 * 7 6]
[0 1 2 9 3 * 7 6]
[0 1 2 * 3 9 7 6]
[0 1 2 * 3 9 7 6]
[0 1 2] [3] [3 9 7 6](一趟排序完成)
//快速分组
int quickClassify(int test[], int left, int right)
{
int i = left;
int j = right;
int x = test[left];
while(i < j)
{
while(test[j] >= x && i < j)
j--;
test[i] = test[j];
if(i >= j) break;
i++;
while(test[i] <= x && i < j)
i++;
test[j] = test[i];
if(i>=j) break;
j--;
}
test[i] = x;
return i;
}
//快速排序
void quickSort(int test[], int left, int right)
{
if(left < right){
int n = quickClassify(test, left, right);
quickSort(test, left, n-1);
quickSort(test, n+1, right);
}
}
快速排序的最好情况是每趟将序列分成两半,正好在表中间,时间复杂度O(nlogn);快速排序最坏的情况是已经排好序,时间复杂度O(n^2)。快速排序是不稳定的排序。
7. 堆排序。
在了解排序原理之前,需要了解“堆”数据结构;
一颗二叉树,如果每个非叶子节点都大(小)于它的所有子节点,那么这颗树就是大(小)根堆;通过构建大根堆和小根堆可以求出序列的最大值或最小值。
堆结构的应用实例:优先队列(PriorityQueue)
堆数据结构进行调整的两个操作:上浮(siftup)和下沉(siftdown)。
排序原理:将序列看成是一颗二叉树。构建大根堆,求出序列的最大值,与序列尾部元素交换位置;再求出剩下元素的最大值。重复以上步骤,直到得到一个从小到大的序列。
例如:数组[3,7,2,9,3,1,0,6] 的分组排序过程:
将序列看成是一棵二叉树,整个排序分为两个部分:新建堆,将最大元与最后元素转换后的重建堆。
程序如下:
#include <stdio.h>
void siftdown(int test[], int k, int m) // 下沉操作
//假设test[k..m]是以r[k]为根的完全二叉树
{
int temp = test[k];
int x = test[k]; //保存当前调整的值
int i = k; // 当前节点
int j = 2 * i; // 子节点
int finished = 0;
while(j <= m && !finished){
if(j < m && test[j] < test[j+1])
j++; // 从左孩子切换到右孩子
if(x >= test[j])
finished = 1;
else{
test[i] = test[j]; //向下移,更新i和j
i = j;
j = 2 * i;
}
}
test[i] = temp;
}
void create_heap(int test[],int length) // 建初堆
{
for(int i = length / 2 - 1; i >= 0; i--){ // 从第一个非叶子节点开始,进行下沉操作
siftdown(test, i, length - 1);
}
}
/*
* 堆排序的三个步骤:
* 1. 建初堆
* 2. 将堆顶元素(最大值)与最后一个元素进行交换
* 3. 堆调整
*/
void heapSort(int test[], int length)
{
create_heap(test, length);
for(int i = length - 1; i >= 1; i--){
int temp = test[0];
test[0] = test[i];
test[i] = temp;
siftdown(test, 0, i-1);
}
}
堆排序时间复杂度在最好和最坏的情况下都是 ;堆排序是不稳定的排序。