C语言排序技术
-
目录
-
交换类排序:冒泡法排序,快速排序
-
插入类排序:简单插入类排序,希尔排序
- 选择类排序 :简单选择排序,堆排序
- 常用的排序方法时间和空间复杂度比较
-
注:代码使用C99编译器
选取3000个(int)随机数进行各种排序并测时间:
#include <stdio.h>
#include <windows.h>
#define N 3000 //取3000个数据进行测量
//测量时间
typedef struct Get_T {
long start;
long end;
long Dt;
}GetTime;
GetTime T1;
void MeasureTime(GetTime* T)
{
static char i = 0;
if (!i)
{
i++;
T->start = GetTickCount();//windows.h
}
else
{
T->end = GetTickCount();
printf("%ldms\n", (T->end) - (T->start)); //ms
i = 0;
}
}
void main(void)
{
int data[N]={0};
//测试函数准确度:
/*
MeasureTime(&T1);
Sleep(1000);
MeasureTime(&T1);
*/
for (int i = 0; i < N; i++)
data[i]= rand()%N;//产生N个0-N之间的随机数
MeasureTime(&T1);
//Cmp1(data);
//INS1(data);
//INS2(data);
//Sec1(data);
//Sec2(data);
MeasureTime(&T1);
for (int i = 0; i < N; i++)
printf("%d",*(data+i));//查看排序结果
system("pause");
}
交换类排序
冒泡排序:一种最简单的交换类排序方法,它是通过两两相邻数据元素的交换逐步将线性表变成有序。冒泡排序每-遍的从前往后扫描都把排序范围内的最大元素沉到了表的底部,每-遍的从后往前扫描,都把排序范围内的最小元素像气泡一样浮到了表的最前面。冒泡排序的名称也由此而来。在最坏情况下,对长度为n的线性表排序,冒泡排序需要经过n/2遍的从前往后的扫描和n/2遍的从后往前扫描,需要比较的次数为n (n-1) /2。
//冒泡排序法
//如果只是向右或向左消除逆序,那么循环次数为N-1
void Comp1(int* p) {
int i, t,j;
for (j = 0; j < N/2; j++)
{
for (i = 0; i<N - 1; i++) //向右消除逆序
if (*(p + i) >= *(p + i + 1))
{
t = *(p + i + 1);
*(p + i + 1) = *(p + i);
*(p + i) = t;
}
for (i = N - 1; i >=0; i--)//向左消除逆序
{
if (*(p + i) <= *(p + i - 1))
{
t = *(p + i - 1);
*(p + i - 1) = *(p + i);
*(p + i) = t;
}
}
}
}
原理:
快速排序:由于冒泡排序法在扫描过程中只对相邻两个元素进行比较,因此,互换两个相邻元素时只能消除一个逆序。如果通过两个(不相邻)元素的交换,能够消除线性表中的多个逆序,就会大大加快排序的速度。下面介绍的快速排序法可以实现通过一次交换而消除多个逆序。(快速排序暂时未实现,给个demo)
//快速排序
void Cmp2(char *ptr)
{
char* i=0, *j=0;
char t = 0,a=0;
char *low = ptr, *high = ptr;
char *data = ptr;
i = ptr + N - 1;
j = ptr;
while (flag < 4)
{
for (; *i;)
{
if (*(j) >= *i)//向左
{
t = *i;
*i = *j;
*j = t;
high = i;
break;
}
i--;
}
for(;*j;)
{
if ( *(i)<= *j )
{
t = *j;
*j = *i;
*i = t;
low = j;
printf("*low = %d\n", *low);
printf("*high = %d\n", *high);
break;
}
j++;
}
if (high == low)
{
for (a = 0; a < N; a++) //---->取出最大
printf("%d\n", *(a + ptr));
i = ptr + N - 1;
j = ptr;
printf("high == left \n");
flag++;
break;
}
}
// system("pause");调试
return;
}
原理:
插入类排序
简单插入类排序:把n个待排序的元素看成是- -个有序表和一个无序表。开始时,有序表只包含一个元素,而无序表包含另外n-1个元素,每次取无序表中的第一一个元素插入到有序表中的正确位置,使之成为增加一个元素的新的有序表。插入元素时,插入位置及其后的记录依次向后移动。最后有序表的长度为n,而无序表为空,此时排序完成。
void INS1(int* Des)
{
for (int i=1; i < N; i++)
{
for (int j = i; j>0 ; j--)
{
if (Des[j] < Des[j-1])
{
int t;
t = Des[j - 1];
Des[j-1] = Des[j];
Des[j] = t;
}
}
}
}
原理:
希尔排序:将整个无序序列分割成若干个小的子序列后分别进行插入排序
子序列的分割方法为:将相隔某个增量d的元素构成-一个子序列。在排序过程中,逐次减小这个增量,最后当d减至1时,进行一次插入排序,排序就完成。
增量序列一般取dt=n/2k (k=1, 2,.,. [log2n]) ,其中n为待排序序列的长度。
//增量序列 Dt=N/2k(取整) (k=1,2,.....[log2N])
//不适用于很多数据 测试数据超过2000个时卡死,低于2000没有问题
void INS2(int* data)
{
#define k 1
for(int dt = N;dt>=0; dt = dt / (2 * k) - 1)//改变增量
for (int i = dt; i < N; i += dt)//分割子序列
{
if (*(data + i) < *(data + i - dt))
{
for (int j = i;j>=0; j -= dt)//对子序列进行个排序
{
if (*(data + j ) < *(data + j-dt))
{
int t;
t = *(data + j -dt);
*(data + j - dt) = *(data + j);
*(data + j) = t;
}
}
}
}
}
原理:
选择类排序
简单选择排序:首先从所有n个待排序的数据元素中选择最小的元素,将该元素与第1个元素交换,再从剩下的n-1个元素中选出最小的元素与第2个元素交换。重复这样的操作直到所有的元素有序为止。
//简单选择排序
void Sec1(int* data)
{
int* min = data;
for (int j = N; j > 0; j--)//未排序的元素
{
for (int i = 1; i < j; i++)
{
if (*min > *(data + i))
{
int t = *min;
*min = *(data + i);
*(data + i) = t;
}
}
min = data++;
}
}
原理:
堆排序:首先将一个无序序列建成堆,然后将堆顶元素(序列中的最大项)与堆中的最后一个元素交换。不考虑已经换到最后的那个元素,将剩下的n- 1个元素重新调整为堆,重复执行此操作,直到所有元素有序为止。(先考虑大根堆情况)
对于数据元素较少的线性表来说,堆排序的优越性并不明显,但对于大量的数据元素来说,堆排序是很有效的。在最坏情况下,堆排序需要比较的次数为0(nlog2n)。
//堆排序
//适合用于大数据
void heapify(int *data, int n, int i)//从(叶结点)父节点出发,比较父结点和子结点的数值 并交换
{
if (i >= n) return;//数据溢出,递归出口
int max = i;//父节点,记录父结点的下标
int left= 2 * i + 1;//子结点
int right= 2 * i + 2;//子节点
if (left<n && *(data+left) > *(data + max))//如果子节编号大于N 溢出
max = left;//左子节点大于父结点
if (right<n && *(data + right) > *(data + max))//如果子节编号大于N 溢出
max = right;//右子节点大于父结点
if (max != i)//父结点和左或右子节点交换
{
int t = *(data +i);
*(data+i) = *(data + max);
*(data + max) = t;
heapify(data,n,max);//当每次向上建立完堆时,需要保证之前的堆(例如叶子节点-堆)任然满足堆的性质,从下往上递归
}
}
/*
关于堆:
定义:
1.完全二叉树 从上往下 从左往右
2.父节点的内容大于子节点的内容
性质:
*父结点=(i-1)/2 i:子节点
*叶子结点最大标号 N-1
*叶子结点的父结点父结点 ((N-1)-1)/2
*左子结点=2*i+1
*右子结点=2*i+2
*/
void Sec2(int *data)
{
for (int i = (N - 2) / 2; i >= 0; i--)//建立堆(从父结点出发)
heapify(data, N, i);//从下到上
for (int i = N - 1; i >= 0; i--) //先左后右 先下后上
{
int t = *(data+i);
*(data + i) = *data;
*data= t;//破坏了根节点的堆
heapify(data, i, 0);//恢复根节点堆的性质
}
}
方法 | 平均时间 | 最坏情况时间 | 辅助存储 |
冒泡排序 | O(![]() | O(![]() | O(1) |
快速排序 | O(nlog2n) | O(![]() | O(log2n) |
插入排序 | O(![]() | O(![]() | O(1) |
选择排序 | O(![]() | O(![]() | O(1) |
堆排序 | O(nlog2n) | O(![]() | O(1) |
程序测试:
使用希尔排序时数据大于2000不稳定