第八章:排序
存储结构:
//记录序列一顺序表存储
#define MAXSIEZ 20 //设记录不超过20个
typedef int KeyType; //设关键字为整数型
//定义每个记录(元素)的结构
Typedef struct{
KeyType key; //关键字
ingoType otherinfo; //其他数据
}
//定义顺序表的结构
Typedef struct{
RedType r[MAXSIZE +1]; //存储顺序表的向量,r[0]一般作为哨兵或者缓冲区
int length; //顺序表的长度
}
1、直接插入排序法:
思想:把序列分成两个部分,有序在前,无序在后,i指向无序的第一个值,j=i-1
指向有序的最后一个值,把第一个无序的元素赋值给哨兵,让他和有序中的值比较大小,直到找到比他小的值就结束,开始插入,把比他大的值都后移一位L.r[j+1] = L.r[j]
//先把未排序的值分支给x
x=a[i];
//从有序序列的最后一个元素开始比较,如果比较之后比关键字大则往后移动
for (j=i-1; j>=0 && x<a[j], j--) //j到最前面和小于关键字就结束
a[j+1] = a[j];
//当找到比关键字小的值后就把第i位置的元素插入到其后面的位置
a[j+1]=x;
//这里为什么是j+1,因为j需要走到比x小的值那里比较,发现比关键字小则循环结束,把他插入到第j元素后面,把第原来j+1的值后移
加上哨兵的算法:
void InsertSort(SqList &L){
int i,j;
for(i=2; i<L.length; ++i){
if (L.r[i].key < L.r[i-1].key){ //比较序号2和1的值大小
//若第i的值小于前面第i-1的值,则插入
L.r[0] = L.r[i]; //复制作为哨兵
//后移
//如果j的值比哨兵大,则哨兵继续向前比较直到找到比他小的值结束
for(j=i-1; L.r[0].key < L.r[j].key; --j){
L.r[j+1] = L.r[j] //把有序序列中的元素向后移动
}//for
L.r[j+1] = L.r[0]; //插入到正确的位置
}//if
}//for
}
算法实现:
#include <stdio.h>
//打印函数
void print_array(int arr[], int n){
for (int i=0; i<n; i++){
printf("%d ",arr[i]);
}
printf("\n");
}
//直接插入排序
void insertion(int arr[], int len){
int i,j,key;
for (i=1; i<len; i++){
key = arr[i]; //哨兵
j=i-1;
while (j>=0 && arr[j]>key){
arr[j+1] = arr[j]; //移动
j--; //继续往前比较
}//while
arr[j+1] = key; //插入
print_array(arr,len);
}//for
}
//主函数
int main(){
int arr[]={5,4,8,6,3,9,11,75};
insertion(arr, 8);
}
2、折半插入排序法:
思想:和直接插入差不多,区别在于第i元素和有序元素比较的方法是用折半插入
void BInserSort(SqList &L){
for(i=2; i<=L.length; ++i){
L.r[0] = L.r[i]; //哨兵
//初始化折半
low = 1;
high = i-1;
//开始折半,找到插入位置
while(low<=high){
mid = (low + high)/2;
if (L.r[0].key < L.r[mid].key) //哨兵和中间点比较
high = mid-1;
else low = mid + 1; //看大小赋值low或high
}//while,当low大于high时循环结束,则high+1就是要插入的位置
//找到插入位置后开始移动元素
for(j=i-1; j>=high+1; j--)
L.r[j+1] = L.r[j];
L.r[high+1] = L.r[0];
}//for
}
3、希尔排序:
思想:
间隔不是1的插入排序,先把一段数据分成五个五个间隔的排序,然后每段中的每个来比较,然后再把小的数值放到前面,五个分段的弄完之后,就再三个分段来搞,最后间隔一个来搞,最后到排序完成
主程序:
void ShellSort(Sqlist &L, int dlta[], int t){
for (k=0; k<t; ++k)
ShellInsert(L, dlta[k]); //设置好增量k的值
}
排序过程
void ShellInsert(SqList &L, int dk){
//对顺序表L进行一趟增量为dk的Shell排序,dk为步长
for(i=dk+1; i<=L.length; ++i){
if (r[i].key < r[i-dk].key){
r[0] = r[i] //哨兵
//移动
for(j=i-dk; j>0 && (r[0].key < r[j].key); j=j-dk) //j的变化就是减去一个间隔的值
r[j+dk] = r[j]; //移动元素也是要移动一个间隔
r[j+dk] = r[0];
}//if
}//for
}
void shell_sort(int arr[], int n){
int i, j, inc, key;
for (inc=n/2; inc>0; inc/=2){
//每一趟常用插入排序
for (i=inc; i<n; i++){
key = arr[i]; //哨兵
for (j=i; j>=inc && key<arr[j-inc]; j-=inc)
arr[j]=arr[j-inc]; //交换
arr[j]=key;
}
print_arr(arr, n);
}
}
4、冒泡排序:
按照序号两两比较,逆序则交换,从头开始一个元素进行比较,然后看大小移动
void bubble_sort(SqList &L){//冒泡排序法
int i,j,m;
RedType x; //创建一个临时存储空一共要走的趟数=元素个数-1间来存放交换时的元素值
for(m=1; m<n-1; m++){ //一共要走的趟数=元素个数-1
for(i=1; i<n-m; i++) //每一趟需要比较的次数=元素个数-趟数
//前后对比,把大的值交换后面
if(L.r[j].key > L.r[j+1].key){
x=L.r[j]; L.r[j]=L.r[j+1]; L.r[j+1]=x;
//交换方法:先把前面的值赋值给x然后把后面的值赋值给前面已经空的地址,然后再把x中存放的前面的值赋值到后面的地址中
}
}//for
}
改进:设置一个flag,如果后面是有序的就会减少比较次数
void bubble_sort(SqList &L){
int m,i,j,flag=1; //设置一个flag值
RedType x;
for (m=1; m<n-1; m++){
flag=0; //循环的条件加多了一个flag,先把flag值置零
for (i=1; i<n-m; i++)
if (L.r[j].key > L.r[j+1].key)
{
flag=1; //如果是逆转则把flag置一
x=L.r[j];
L.r[j]=L.r[j+1];
L.r[j+1]=x;
}
}
}
void print_arr(int arr[], int length, int flag){
//输出每个元素的值
for (int i=0; i<length; i++){
printf("%d ", arr[i]);
}
//看是否有转换并输出
printf("%s\n", flag?"flag":"no flag");
}
//交换
void swap(int *a, int *b){
int temp = *a;
*a = *b;
*b = temp;
}
void bubble_sort(int arr[], int length){
//比较的趟数,当只有一个元素就不需要比较
for (int i=length; i>1; i--){
int flag = 0; //是否发生转换
//从第一个开始比较
//每趟需要比较次数逐渐减少
for (int j=1; j<i; j++){
if (arr[j-1]>arr[j]){
swap(&arr[j-1], &arr[j]);
flag=1;
}
}
print_arr(arr, length, flag);
if (!flag)
break;
}
}
int main(){
int arr[]={9,3,5,1,7};
print_arr(arr, 5, 0);
bubble_sort(arr, 5);
return 0;
}
5、快速排序:
改进思路:
由于需要空间太大,则改进为另外一种方法
把序号1元素赋值给序号0,然后定义序号2元素为low,最后一个元素为high,由于序号1空了,则需要从后面找一个比原序号2小的值填到空的序号1的位置,如果后面的值移动到了前面出现了空位,则需要从前面开始找
//找中心点的算法
int Partition(SqList &L, int low, int high){
//一开始
L.r[0] = L.r[low]; //哨兵
pivotkey = L.r[low].key;//最开始中心点的值为low的值
while (low<high){//low和high重叠的时候就结束
//先从high这边开始查找有没有比中心点小的值把他移动到low这里来
while (low<high && L.r[high].key >= pivotkey)
--high;//没找到比中心点小的值,high自减,继续循环
L.r[low] = L.r[high];
//当high这边空出时,就要从low这边开始查找比中心点大的时移动到high
while (low<high && L.r[low].key <= pivotkey)
++low;
L.r[high] = L.r[low];
}//while
//当上面两个循环都结束后,把序号0的元素的值赋值给low输出作为中心点
L.r[low] = L.r[0];
return low;
}
//直接排序算法实现
void main(){
QSort(L,1,L.length); //递归算法实现排序
}
//使用递归算法对顺序表排序
void QSort(Sqlist &L,int low, int high){
if (low<high){//分段的顺序表的元素个数大于1,low和high不重叠
pivotloc = Partition(L,low,high);
//将L.r[low...high]一分为二,pivotloc为中心点元素排序的位置
QSort(L,low,pivotloc-1); //对元素较小的子表递归排序
QSort(L,pivotoc+1,high); //对元素较大的子表递归排序
}//if
}
6、简单选择排序:
**思想:**就是从一个表n个元素中每次找到一个最大或者最小的值放到最后或者最前,然后再从n-1个元素中继续之前的排序
从第一个开始逐个对比找出比他小的值,相互交换,有n个元素则需要排序n-1趟排序
void SelectSort(SqList &K){
for(i=1;i<L.length;i++){//从第一个元素开始和后面的元素进行比较
k=i; //先把第一个元素赋值给k
for(j=i+1;j<L.length;j++) //从第i的后一个元素开始比较
if (L.r[j].key < L.r[k].key) //k大于j的值
k=j; //记录最小值给k
if (k!=i)
L.r[i]交换L.r[k];
}
}
//交换
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void selection_sort(int arr[], int n){
int i,j,min;
for (i=0;i<n;i++){
min=i; //把第一个元素复制给哨兵
for (j=i+1;j<n;j++){
if (arr[j]<arr[min]) //i和i+1的元素比较大小
min=j; //把更小的序号赋值给min
}//for_2
//交换最小值
swap(&arr[min], &arr[i]);
print_arr(arr,n);
}//for_1
}//selection
7、堆排序:
- 思想:分为大根堆(大值为根)和小根堆(小值为根),用二叉树表示,父结点的值一定大于或者小于左右子树的值
- 调整:在小根堆里面,把一个小根堆的堆顶元素输出到顺序表中,然后把序号最后的结点放在根结点上,然后比较其和左右子树的值的大小,如果大于子树的值则调下去,然后重复,直到形成有序序列
//堆的维护:
void heapify(int arr[], int n, int i){
//已知父结点和左右子结点的序号关系
int largest = i;
int lson = i*2+1;
int rson = i*2+2;
//大根堆
//比较父结点和左右结点的大小
if (lson<n && arr[largest]<arr[lson])
largest = lson; //左子树大,则向上移动
if (rson<n && arr[largest]<arr[rson])
largest = rson; //右子树大,则向上移动
//前面把largst赋值为i,当largest不为i说明他比自己的左右子树小,所以交换元素值
if (largest !=i ){
swap(&arr[largest], &arr[i]);
//交换后再判断是否满足大根堆的性质,不满足则递归
heapify(arr, n, largest);
}
}
从最后一个叶子结点n的父结点n/2开始,n/2结点和其子树比较大小,然后交换,在到第n/2-1个结点和其子树比较,一直到整棵树的根结点处,然后再从上往下比较交换,直到最小或者最大值到二叉树的根就输出这个根,然后把最后一个结点放到根的位置上
//堆排序
void heap_sort(int arr[], int n){
//建堆
int i;
//n不是下标,是数组长度,转换成下标还要减1
for (i=n/2-1; i>=0; i--) //n/2-1是开始处理的第一个元素的父结点的下标
heapify(arr, n, i);
//排序
//原本的元素有n个,那么剩余元素有n-1个需要比较
for (i=n-1; i>0; i--){ //排序从最后一个元素开始
swap(&arr[i], &arr[0]); //最后一个元素和堆顶元素进行交换
heapify(arr, i, 0); //再进行一次调整
}
}
8、归并排序:
//合并
void merge(int arr[], int tempArr[], int left, int mid, int right){
//标记左半区第一个未排序的元素
int l_pos=left;
//标记左半区第一个未排序的元素
int r_pos=mid+1;
//临时数组元素的下标
int pos=left;
//合并
//判断左右第一个值谁大谁小,把小的赋值给新数组
while (l_pos<=mid && r_pos<right)
{
if (arr[l_pos] < arr[r_pos]) //左半区第一个元素更小
tempArr[pos++] = arr[l_pos++]; //把小的元素复制给临时数组,然后各自后移一位
else (arr[l_pos] > arr[r_pos]) //右半区第一个元素更小
tempArr[pos++] = arr[r_pos++];
}//while
//如果合并完左边的元素后,右边还有剩余元素,同时右边是有序的,可以直接放到整个有序数组中
//合并左半区剩余的元素
while (l_pos <= mid)
tempArr[pos++] = arr[l_pos++];
//合并右半区剩余的元素
while (r_pos <= right)
tempArr[pos++] = arr[r_pos++];
//把临时数组中合并后的元素复制回原来的数组
while (left <= right)
{
arr[left]=tempArr[left];
left++;
}
}
//归并排序
void msort(int arr[],int tempArr[], int left, int right){
//如果只有一个元素,那么不需要继续划分,直接就
if (left<right){
//找中间点
int mid = (left + right)/2;
//递归划分左半区
msort(arr, tempArr, left, mid);
//递归划分右半区
msort(arr, tempArr, mid+1, right);
//合并已经排序好的部分
merge(arr, tempArr, left, mid, right);
}//if
}
void merge_sort(int arr[], int n){
//辅助数组分配空间
int *tempArr = (int*)malloc(n*sizeof(int));
//如果分配成功
if (tempArr)
{
//进行排序
msort(arr, tempArr, 0, n-1);
free(tempArr);
}
else
{
printf("error");
}
}