冒泡排序
//冒泡排序:时间复杂度 O(n2)
#define N 10
int main(int argc, char const *argv[])
{
int a[N];
int i,j=0,t;
//赋初值
printf("\033[32m 请输入要排序的 10 个数\033[0m\n");
for(i=0;i<N;i++)
scanf("%d",a+i);
//冒泡排序(升序):每遍历一趟都可以冒出一个最大的数上来,遍历 N-1 趟就能完成排序
for(i=N-1;i>0;i--)
for(j=0;j<i;j++)
{
if(a[j]>a[j+1]) //必后面的大则向后面走
{
//在这个判断中我们可以对冒泡排序进行升级,也就是如果遍历一趟下来都没发生交换,则此时数据已经是有序的了,就可以退出循环节省时间
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
//输出
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("\n");
return 0;
}
选择排序
//选择排序 时间复杂度 O(n2)
#define N 10
int main(int argc, char const *argv[])
{
int a[N];
int i,j,z;
//赋初值
printf("\033[32m 请输入要排序的 10 个数\033[0m\n");
for(i=0;i<N;i++)
scanf("%d",a+i);
//选择排序(升序):遍历 0-N,将 a[i]后面的数和 a[i]进行比较,如果小于a[i]就与之交换
for(i=0;i<N;i++)
for(j=i+1;j<N;j++)
{
if(a[i]>a[j])
{
z=a[i];
a[i]=a[j];
a[j]=z;
}
}
//打印
for(i=0;i<N;i++)
printf("\033[31m%d \033[0m",a[i]);
printf("\n");
return 0;
}
简单插入排序
//插入排序:算法复杂度(n2)
int main(int argc, char const *argv[])
{
int a[N];
int i,j,min;
printf("输入数组的数值,数组大小为%d\n",N);
for(i=0;i<N;i++)
scanf("%d",a+i);
//插入排序(升序)
/*
1.从第一个元素开始,该元素可以认为已经被排序;
2.取出下一个元素,在已经排序的元素序列中从后向前扫描;
3.如果该元素(已排序)大于新元素,将该元素移到下一位置;
4.重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置;
5.将新元素插入到该位置后;
重复步骤 2~5。
*/
for(i=1;i<N;i++) //第一个元素默认已经被排序,从第二个元素开始
{
min = a[i]; //记录要插入的数
for(j=i-1;j>=0;j--) //遍历已经排序好的元素,找到合适的插入位置
{
if(min<a[j]) //如果要插入的数小于 a[j],则说明这个要元素在 a[j]前面,那么 a[j]要向后移
{
a[j+1]=a[j];
}
else //否则插入在 a[j]后面
{
a[j+1]=min;
break;
}
if(min<a[j]&&j==0) //j==0 了表示要插入的数比所有排序好的数都小
{
a[j]=min; //此时插入在 a[0]的位置即可
break;
}
}
}
//打印输出
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("\n");
return 0;
}
再来变一个样子展示插入排序:
#include <stdio.h>
#define N 10
void insertionSort(int *a);
int main(int argc, char const *argv[])
{
int a[N];
int i,j,min;
printf("输入数组的数值,数组大小为%d\n",N);
for(i=0;i<N;i++)
scanf("%d",a+i);
insertionSort(a); //插入排序
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("\n");
}
void insertionSort(int *a)
{
int i;
int preIndex, current;
for (i = 1; i < N; i++)
{
preIndex = i - 1; //保存排序好的数据的最大下标
current = a[i]; //保存带插入的数
while (preIndex >= 0 && a[preIndex] > current)
{
a[preIndex + 1] = a[preIndex];
preIndex--;
}
a[preIndex + 1] = current;
}
}
快速排序
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序
#include <stdio.h>
#define N 10
void F(int *a,int i,int NI);
int main(int argc, char const *argv[])
{
int a[N];
int i,j=0;
printf("输入数组数值\n");
for(i=0;i<N;i++)
scanf("%d",a+i);
F(a,j,N-1); //快排
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("\n");
return 0;
}
/**
* @brief 快速排序算法(升序) :快速排序使用分治法来把一个串(list)分
为两个子串(sub-lists)。具体算法描述如下:
1.从数列中挑出一个元素,称为 “基准”(pivot);
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准
值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准
就处于数列的中间位置。这个称为分区(partition)操作;
3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子
数列排序。
*
* @param a 需要排序的数组
* @param i 需要排序的区间左边界下标
* @param NI 需要排序的区间的右边界下标
*/
void F(int *a,int i,int NI)
{
int x=i; //记录左边界下标
int y=NI; //记录右边界下标
if(NI>i) //递归结束条件就是右边界小于左边界
{
int t; //用来记住基准值,那么基准值的位置就是空位
t=a[x]; //默认基准值为 a[i];
while(x<y) //左游标小于右游标
{
while(x<y&&a[y]>t) //如果左游标小于右游标且右游标对应值大于基准值
{
y--; //右游标向前移
}
a[x]=a[y]; //说明遇到右游标的值小于基准值了,则将右游标的数放到空位,那 a[y]现在就是空位了
while(x<y&&a[x]<=t) //如果左游标小于右游标且左游标对应值小于基准值
{
x++; //左游标向后移
}
a[y]=a[x]; //说明遇到左游标的值大于基准值了,则将左游标的数放到空位,那么现在 a[x]就变空位了
}
a[x]=t; //最后把基准值放到空位,这样之后就保证了基准值之前的值都比基准值小,基准值之后的值都比基准值大
F(a,i,x-1); //递归基准值之前的那部分
F(a,x+1,NI); //递归基准值之后的那部分
}
else
{
return;
}
}
希尔排序
1959 年
Shell
发明,第一个突破
O(n
2
)
的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序
。
#include <stdio.h>
#define N 9
/**
* @brief 希尔排序(升序) 先将整个待排序的记录序列分割成为若干子序列分
别进行直接插入排序,具体算法描述:
* 1.选择一个增量序列 t1,t2,…,tk,其中 ti>tj,tk=1;
2.按增量序列个数 k,对序列进行 k 趟排序;
3.每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的
子序列,分别对各子表进行直接插入排序。
仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个
序列的长度。
*
* @param a 待排序的数组
* @param len 数组元素个数
*/
void shellSort(int *a, int len)
{
int i, j, k, tmp, gap; //gap 为步长
for (gap = len / 2; gap > 0; gap /= 2) //(作用:缩小步长)这样数组就被分为了 gap 组
{
// 步长初始化为数组长度的一半,每次遍历后步长减半,
for (i = 0; i < gap; ++i) //(作用:遍历每一组)
{ // 变量 i 为每次分组的第一个元素下标
for (j = i + gap; j < len; j += gap) //(作用:插入排序当前组) //a[i]是第一个元素,从第二个元素开始
{
//这里就是插入排序的步骤了
tmp = a[j]; // 备份 a[j]的值,待插入的值
k = j - gap; // k 初始化为 j 的前一个元素的下标(与 j相差 gap 长度)
while (k >= 0 && a[k] > tmp)
{
a[k + gap] = a[k]; // 将在 a[i]前且比 tmp 的值大的元素向后移动一位
k -= gap;
}
a[k + gap] = tmp;
}
}
}
}
int main(int argc, char const *argv[])
{
int a[N];
int i,j,z;
//赋初值
printf("\033[32m 请输入要排序的%d 个数\033[0m\n",N);
for(i=0;i<N;i++)
scanf("%d",a+i);
shellSort(a,N); //希尔排序
//打印
for(i=0;i<N;i++)
printf("\033[31m%d \033[0m",a[i]);
printf("\n");
return 0;
}
归并排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为 2-
路归并。
#include <stdio.h>
#define N 10
// 递归的方式实现归并排序
// 实现归并,并把结果存放到 list1
void merging(int *list1, int list1_size, int *list2, int list2_size)
{
int i,j,k, m;
int temp[N]; //用以保存排序好的部分
i = j = k = 0;
while(i < list1_size && j < list2_size)
{
//比较左半边和右半边元素
if(list1[i] < list2[j]) //右边元素大于左边
{
temp[k] = list1[i]; //记录左边元素
k++; //记录数组元素个数加一
i++; //左++
}
else //右边元素小于左边元素
{
temp[k++] = list2[j++];
}
}
while(i < list1_size) //这种情况是因为上面的 while 是因为右半边元素遍历完了退出循环还有左边元素未加入 tmp
{
temp[k++] = list1[i++];
}
while(j < list2_size)//这种情况是因为上面的 while 是因为左半边元素遍历完了退出循环还有右边元素未加入 tmp
{
temp[k++] = list2[j++];
}
for(m = 0;m < (list1_size + list2_size);m++) //同步到 listt1 上,相当于合并了左右两边元素并排序好了
{
list1[m] = temp[m];
}
}
/**
* @brief 归并排序(升序):
* 1.把长度为 n 的输入序列分成两个长度为 n/2 的子序列;
2.对这两个子序列分别采用归并排序;
3.将两个排序好的子序列合并成一个最终的排序序列。
*
* @param k 待排序数组
* @param n 元素个数
*/
void MergeSort(int k[], int n)
{
if(n > 1)
{
/*
*list1 是左半部分,list2 是右半部分
*/
int *list1 = k; //指向左半边元素
int list1_size = n/2; //左半边个数
int *list2 = k + list1_size; //指向右半边元素
int list2_size = n - list1_size; //右半边元素个数
MergeSort(list1, list1_size); //递归拆分左半边
MergeSort(list2, list2_size); //递归拆分右半边
// 把两个合在一起
merging(list1, list1_size, list2, list2_size); //对左半边和右半边进行整体排序
}
}
int main(int argc, char const *argv[])
{
int a[N];
int i,j=0,t;
//赋初值
printf("\033[32m 请输入要排序的 10 个数\033[0m\n");
for(i=0;i<N;i++)
scanf("%d",a+i);
MergeSort(a, N); //归并排序
//输出
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("\n");
return 0;
}
基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
#include <stdlib.h>
#include<stdio.h>
#define N 10 //数组个数
#define KEYNUM_10 10 //关键字个数,这里为整形位数
/**
* @brief 找到 num 的从低到高的第 pos 位值
*
* @param num 数据
* @param pos num 的第几位
* @return int 返回 num 的从低到高的第 pos 位值
*/
int H(int num,int pos)
{
int temp = 1;
int i;
for (i = 0; i < pos - 1; i++)
temp *= 10;
return (num / temp) % 10;
}
/**
* @brief 基数排序(升序):
* 1.取得数组中的最大数,并取得位数;
2.a 为原始数组,从最低位开始取每个位组成 radix 数组;
3.对 radix 进行计数排序(利用计数排序适用于小范围数的特点)
* @param a 待排序数组
* @param n 元素个数
*/
void F(int* a, int n)
{
int i,pos,num,index;
int *b[10]; //分别为 0~9 的序列空间
for (int i = 0; i < 10; i++)
{
*(b+i) = (int *)malloc(sizeof(int) * (n + 1));
b[i][0] = 0; //index 为 0 处记录这组数据的个数
}
for (pos = 1; pos <= KEYNUM_10; pos++) //从个位开始到第KEYNUM_10 位
{
for (i = 0; i < n; i++) //分配过程
{
num = H(a[i], pos); //num 保存 a[i]的第 pos 位的值
index = ++b[num][0]; //该记录栈的个数加 1
b[num][index] = a[i]; //记录 a[i]
}
for (int i = 0, j =0; i < 10; i++) //收集 b 中经过基数排序保存的数据
{
for (int k = 1; k <= b[i][0]; k++)
a[j++] = b[i][k];
b[i][0] = 0; //复位
}
}
for (int i = 0; i < 10; i++) //释放申请的空间
{
free(*(b+i));
}
}
int main(int argc, char const *argv[])
{
int a[N];
int i,j=0;
printf("输入数组数值\n");
for(i=0;i<N;i++)
scanf("%d",a+i);
F(a,N); //基数排序
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("\n");
return 0;
}
计数排序
计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围
的整数
,类似于哈希表思想。
和计数排序类似的就是桶排序,如果说计数排序是哈希表的线性存储,那么桶排序就是哈希表的线性存储。
#include <stdio.h>
#define N 10
void F(int *a,int n);
int main(int argc, char const *argv[])
{
int a[N];
int i,j=0;
printf("输入数组数值\n");
for(i=0;i<N;i++)
scanf("%d",a+i);
F(a,N); //计数排序
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("\n");
return 0;
}
/**
* @brief 计数排序(升序):
* 1.找出待排序的数组中最大和最小的元素;
2.统计数组中每个值为 i 的元素出现的次数,存入数组 C 的第 i 项;
3.对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相
加);
4.反向填充目标数组:将每个元素 i 放在新数组的第 C(i)项,每放一个
元素就将 C(i)减去 1。
*
* @param a 待排序数组
* @param n 元素个数
*/
void F(int *a,int n)
{
int i,j,min,max;
max=min=a[0];
for(i=1;i<n;i++) //找到最大值和最小值
{
if(a[i]>max)
max=a[i];
if(a[i]<min)
min=a[i];
}
int b[10000]={0}; //max-min 的范围在 0-10000 之间
for(i=0;i<n;i++)
b[a[i]-min]++; //记录 a[i]-min 的值的个数
i=0;
for(j=0;j<=(max-min);j++) //遍历 0 到 max-min
{
if(b[j] != 0) //记录个数不为 0
{
while(b[j]--) //b[j]为保存值为 j 的元素个数
{
a[i++]=j+min; //把值经过排序还原到 a
}
}
}
}
堆排序
堆排序(Heapsort
)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
#include <stdio.h>
#include <malloc.h>
#define N 10
void HeapAdjust(int a[],int s,int m)//一次筛选的过程
{
int rc,j;
rc=a[s]; //记录该节点值
//下面的步骤不仅是为了将最大值移到根节点,也在调整二叉树使其满足堆的性质
for(j=(2*s+1);j<=m;j=j*2)//通过循环沿较大的孩子结点向下筛选,a[j]是左孩子,a[j+1]是右孩子
{
if(j<m&&a[j]<a[j+1]) j++;//说明右孩子大于左孩子值,那么 j 保存右孩子下标。j<m 是为了保证 j+1<=m
if(rc>a[j]) break; //父节点值比子节点大退出
a[s]=a[j]; //父节点保存子节点值
s=j; //是保存子节点下标,用来交换子节点和父节点的值
}
a[s]=rc;
}
/**
* @brief 堆排序(升序):用二叉树的思想去看待这个待排序的数组,a[0]是根
节点,a[1]是左节点 a[2]是右节点---a[i]节点的左孩子是
* a[2*i+1],右孩子是 a[2*i+2],父节点是 a[i/2]或 a[i/2-1]主要
看是左节点还是右节点
* 1.将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的
无序区;
2.将堆顶元素 R[1]与最后一个元素 R[n]交换,此时得到新的无序区
(R1,R2,……Rn-1)和新的有序区(Rn),且满足 R[1,2…n-1]<=R[n];
3.由于交换后新的堆顶 R[1]可能违反堆的性质,因此需要对当前无序区
(R1,R2,……Rn-1)调整为新堆,
然后再次将 R[1]与无序区最后一个元素交换,得到新的无序区
(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。
不断重复此过程直到有序区的元素个数为 n-1,则整个排序过程完成
*
* @param a 待排序的数组
* @param n 元素个数-1
*/
void HeapSort(int a[],int n)
{
int temp,i,j;
for(i=n/2;i>=0;i--)//通过循环初始化顶堆(此次循环是从 n/2 到 0 的,也就是从最后一个不是叶子节点的节点开始遍历到根节点)
HeapAdjust(a,i,n);
//经过上面步骤后,此时的根节点保存的就是最大值,同时这个二叉树满足了堆的性质,也就是父节点要大于子节点
for(i=n;i>0;i--)
{
temp=a[0]; //temp 保存根节点值
a[0]=a[i]; //和最后一个元素交换值
a[i]=temp;//将堆顶记录与未排序的最后一个记录交换
HeapAdjust(a,0,i-1);//重新调整为顶堆,每经过一次循环待排序的元素就减一
}
}
int main(int argc, char const *argv[])
{
int a[N];
int i,j,min;
printf("输入数组的数值,数组大小为%d\n",N);
for(i=0;i<N;i++)
scanf("%d",a+i);
HeapSort(a,N-1); //堆排序
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("\n");
return 0;
}