插入排序 Insert Sort
算法思想:将数据按照一定的顺序一个一个的插入到有序的表中,最终得到的序列就是已经排序好的数据。
代码实现:
void InsertSort(int a[], int n)
{
for(int i= 1; i<n; i++){
if(a[i] < a[i-1]){//若第 i 个元素大于 i-1 元素则直接插入;反之,需要找到适当的插入位置后在插入。
int j= i-1;
int x = a[i];
while(j>-1 && x < a[j]){ //采用顺序查找方式找到插入的位置,在查找的同时,将数组中的元素进行后移操作,给插入元素腾出空间
a[j+1] = a[j];
j--;
}
a[j+1] = x; //插入到正确位置
}
print(a,n,i);//打印每次排序后的结果
}
}
希尔排序 Shell Sort
算法思想:将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。
代码实现:
void ShellShort(int a[],int n){
int d,i,j; //d为增量
for (d=n/2 ;d>=1 ;d=d/2) {
for (i=d;i<n;++i){
if (a[i]<a[i-d]){
int x=a[i];
for (j=i-d;j>=0&&x<=a[j];j-=d)
a[j+d]=a[j];
a[j+d]=x;
}
}
print(a,n,d);//打印每次排序后的结果
}
}
冒泡排序 Bubble Sort
算法思想:将无序表中的所有记录,通过两两比较关键字,得出升序序列或者降序序列。
代码实现:
void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void bubbleSort(int a[],int n){
int key;
//有多少记录,就需要多少次冒泡,当比较过程,所有记录都按照升序排列时,排序结束
for (int i = 0; i < n; i++){
key=0;//每次开始冒泡前,初始化 key 值为 0
//每次起泡从下标为 0 开始,到 n-i 结束
for (int j = 0; j+1<n-i; j++){
if (a[j] > a[j+1]){
key=1;
swap(&a[j], &a[j+1]);
}
}
//如果 key 值为 0,表明表中记录排序完成
if (key==0) {
break;
}
print(a,n,i);//打印每次排序后的结果
}
}
快速排序 QSort
算法思想:通过一次排序将整个无序表分成相互独立的两部分,其中一部分中的数据都比另一部分中包含的数据的值小,然后继续沿用此方法分别对两部分进行同样的操作,直到每一个小部分不可再分,所得到的整个序列就成为了有序序列。
代码实现:
int Partition(int a[], int low, int high){
int x=a[low];
while(low<high){
while(low<high&&a[high]>=x) --high; //比x小的元素移动到x左面
a[low]=a[high];
while(low<high&&a[low]<=x) ++low; //比x大的元素移动到x右面
a[high]=a[low];
}
a[low]=x;
print(a,7);//打印每次排序后的结果
return low;
}
void QuickSort(int a[],int low,int high){
if (low<high){
int pivotpos=Partition(a,low,high);
QuickSort(a,low,pivotpos-1); //左区
QuickSort(a,pivotpos+1,high); //右区
}
}
选择排序 SelectSort
算法思想:对于具有 n 个记录的无序表遍历 n-1 次,第 i 次从无序表中第 i 个记录开始,找出后序关键字中最小的记录,然后放置在第 i 的位置上。
代码实现:
void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void SelectSort(int a[],int n){
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!=j)
swap(&a[i],&a[min]); //交换元素
}
print(a,n,i+1);//打印每次排序后的结果
}
}
堆排序
算法思想:在含有 n 个元素的序列中,如果序列中的元素满足下面其中一种关系时,此序列可以称之为堆。
- ki ≤ k2i 且 ki ≤ k2i+1(在 n 个记录的范围内,第 i 个关键字的值小于第 2i 个关键字,同时也小于第 2i+1 个关键字)
- ki ≥ k2i 且 ki ≥ k2i+1(在 n 个记录的范围内,第 i 个关键字的值大于第 2i 个关键字,同时也大于第 2i+1 个关键字)
代码实现:http://c.biancheng.net/view/3448.html
#include <stdio.h>
#include <stdlib.h>
#define MAX 9
//单个记录的结构体
typedef struct {
int key;
}SqNote;
//记录表的结构体
typedef struct {
SqNote r[MAX];
int length;
}SqList;
//将以 r[s]为根结点的子树构成堆,堆中每个根结点的值都比其孩子结点的值大
void HeapAdjust(SqList * H,int s,int m){
SqNote rc=H->r[s];//先对操作位置上的结点数据进行保存,放置后序移动元素丢失。
//对于第 s 个结点,筛选一直到叶子结点结束
for (int j=2*s; j<=m; j*=2) {
//找到值最大的孩子结点
if (j+1<m && (H->r[j].key<H->r[j+1].key)) {
j++;
}
//如果当前结点比最大的孩子结点的值还大,则不需要对此结点进行筛选,直接略过
if (!(rc.key<H->r[j].key)) {
break;
}
//如果当前结点的值比孩子结点中最大的值小,则将最大的值移至该结点,由于 rc 记录着该结点的值,所以该结点的值不会丢失
H->r[s]=H->r[j];
s=j;//s相当于指针的作用,指向其孩子结点,继续进行筛选
}
H->r[s]=rc;//最终需将rc的值添加到正确的位置
}
//交换两个记录的位置
void swap(SqNote *a,SqNote *b){
int key=a->key;
a->key=b->key;
b->key=key;
}
void HeapSort(SqList *H){
//构建堆的过程
for (int i=H->length/2; i>0; i--) {
//对于有孩子结点的根结点进行筛选
HeapAdjust(H, i, H->length);
}
//通过不断地筛选出最大值,同时不断地进行筛选剩余元素
for (int i=H->length; i>1; i--) {
//交换过程,即为将选出的最大值进行保存大表的最后,同时用最后位置上的元素进行替换,为下一次筛选做准备
swap(&(H->r[1]), &(H->r[i]));
//进行筛选次最大值的工作
HeapAdjust(H, 1, i-1);
}
}
int main() {
SqList * L=(SqList*)malloc(sizeof(SqList));
L->length=8;
L->r[1].key=49;
L->r[2].key=38;
L->r[3].key=65;
L->r[4].key=97;
L->r[5].key=76;
L->r[6].key=13;
L->r[7].key=27;
L->r[8].key=49;
HeapSort(L);
for (int i=1; i<=L->length; i++) {
printf("%d ",L->r[i].key);
}
return 0;
}
归并排序 Merge
算法思想:先将所有的记录完全分开,然后两两合并,在合并的过程中将其排好序,最终能够得到一个完整的有序表。
实现代码:
int *B=(int *)malloc(sizeof(int)); //辅助数组
void Merge(int a[],int low,int mid,int high){
int i,j,k;
for ( k = low; k <=high ; ++k) {
B[k]=a[k]; //B数组复制数组a
}
for ( i = low, j=mid+1,k=i; i <=mid&&j<=high ; k++) {
if (B[i]<=B[j])
a[k]=B[i++];
else
a[k]=B[j++];
}
while(i<=mid) a[k++]=B[i++];
while(j<=high) a[k++]=B[j++];
}
void MergeSort(int a[],int low,int high){
if (low<high){
int mid=(low+high)/2;
MergeSort(a,low,mid);
MergeSort(a,mid+1,high);
Merge(a,low,mid,high);
}
}
基数排序
算法思想:对关键字进行“分配”与“收集”两种操作即可完成。
代码实现:http://c.biancheng.net/view/vip_3450.html
#include <stdio.h>
#include <stdlib.h>
#define MAX_NUM_OF_KEY 8//构成关键字的组成部分的最大个数
#define RADIX 10 //基数,例如关键字是数字,无疑由0~9组成,基数就是10;如果关键字是字符串(字母组成),基数就是 26
#define MAX_SPACE 10000
//静态链表的结点结构
typedef struct{
int data;//存储的关键字
int keys[MAX_NUM_OF_KEY];//存储关键字的数组(此时是一位一位的存储在数组中)
int next;//做指针用,用于是静态链表,所以每个结点中存储着下一个结点所在数组中的位置下标
}SLCell;
//静态链表结构
typedef struct{
SLCell r[MAX_SPACE];//静态链表的可利用空间,其中r[0]为头结点
int keynum;//当前所有的关键字中最大的关键字所包含的位数,例如最大关键字是百,说明所有keynum=3
int recnum;//静态链表的当前长度
} SLList;
typedef int ArrType[RADIX];//指针数组,用于记录各子序列的首尾位置
//排序的分配算法,i表示按照分配的位次(是个位,十位还是百位),f表示各子序列中第一个记录和最后一个记录的位置
void Distribute(SLCell *r,int i,ArrType f,ArrType e){
//初始化指针数组
for (int j=0; j<RADIX; j++) {
f[j]=0;
}
//遍历各个关键字
for (int p=r[0].next; p; p=r[p].next) {
int j=r[p].keys[i];//取出每个关键字的第 i 位,由于采用的是最低位优先法,所以,例如,第 1 位指的就是每个关键字的个位
if (!f[j]) {//如果只想该位数字的指针不存在,说明这是第一个关键字,直接记录该关键字的位置即可
f[j]=p;
}else{//如果存在,说明之前已经有同该关键字相同位的记录,所以需要将其进行连接,将最后一个相同的关键字的next指针指向该关键字所在的位置,同时最后移动尾指针的位置。
r[e[j]].next=p;
}
e[j]=p;//移动尾指针的位置
}
}
//基数排序的收集算法,即重新设置链表中各结点的尾指针
void Collect(SLCell *r,int i,ArrType f,ArrType e){
int j;
//从 0 开始遍历,查找头指针不为空的情况,为空表明该位没有该类型的关键字
for (j=0;!f[j]; j++);
r[0].next=f[j];//重新设置头结点
int t=e[j];//找到尾指针的位置
while (j<RADIX) {
for (j++; j<RADIX; j++) {
if (f[j]) {
r[t].next=f[j];//重新连接下一个位次的首个关键字
t=e[j];//t代表下一个位次的尾指针所在的位置
}
}
}
r[t].next=0;//0表示链表结束
}
void RadixSort(SLList *L){
ArrType f,e;
//根据记录中所包含的关键字的最大位数,一位一位的进行分配与收集
for (int i=0; i<L->keynum; i++) {
//秉着先分配后收集的顺序
Distribute(L->r, i, f, e);
Collect(L->r, i, f, e);
}
}
//创建静态链表
void creatList(SLList * L){
int key,i=1,j;
scanf("%d",&key);
while (key!=-1) {
L->r[i].data=key;
for (j=0; j<=L->keynum; j++) {
L->r[i].keys[j]=key%10;
key/=10;
}
L->r[i-1].next=i;
i++;
scanf("%d",&key);
}
L->recnum=i-1;
L->r[L->recnum].next=0;
}
//输出静态链表
void print(SLList*L){
for (int p=L->r[0].next; p; p=L->r[p].next) {
printf("%d ",L->r[p].data);
}
printf("\n");
}
int main(int argc, const char * argv[]) {
SLList *L=(SLList*)malloc(sizeof(SLList));
L->keynum=3;
L->recnum=0;
creatList(L);//创建静态链表
printf("排序前:");
print(L);
RadixSort(L);//对静态链表中的记录进行基数排序
printf("排序后:");
print(L);
return 0;
}
内部排序算法的优劣
排序方法 | 平均时间 | 最坏情况 | 存储空间 | 稳定性 |
---|---|---|---|---|
简单排序 | O(n^2) | O(n^2) | O(1) | ---- |
快速排序 | O(logn) | O(n^2) | O(logn) | 不稳定 |
堆排序 | O(logn) | O(logn) | O(1) | ---- |
归并排序 | O(logn) | O(logn) | O(n) | 稳定 |
基数排序 | O(d*n) | O(d*n) | O(d*n) | 稳定 |
- 上表中的简单排序包含出希尔排序之外的所有插入排序,冒泡排序和简单选择排序。
- 简单排序中,无序表记录n较少直接插入为最佳。
- 所有的排序算法中单就平均时间性能上分析,快速排序算法最佳,其运行所需的时间最短,但其在最坏的情况下的时间性能不如堆排序和归并排序。
- 堆排序和归并排序相比较,当无序表中记录的数量 n 较大时,归并排序所需时间比堆排序短,但是在运行过程中所需的辅助存储空间更多(以空间换时间)。
- 从基数排序的时间复杂度上分析,该算法最适用于对 n 值很大但是关键字较小的序列进行排序。
- 选择排序、快速排序和希尔排序都不是稳定的排序算法;而冒泡排序、插入排序、归并排序和基数排序都是稳定的排序算法。