05-c语言排序

一、冒泡排序
时间复杂度:平均情况:O(n^2) 最好情况:O(n)
空间复杂度:O(1)
稳定性:稳定
主要思路:
1.比较相邻的元素。如果第一个比第二个大,就交换它们两个。
2.对每一个相邻元素做同样的工作,从开始第一对到结尾的每一对。在这一 点,最后的元素应该会是最大的数。
3.针对多有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,知道没有任何一对数字需要比较。
图解:

扫描二维码关注公众号,回复: 11208086 查看本文章

代码:

/*********************冒泡排序******************************/
#include<stdio.h>
//#include <assert.h>
//#include <stdbool.h>

#define bool char
#define true 1
#define false 0

//排序函数
void Bubble_sort(int *arr, int len)
{
    //assert(arr != NULL);
    int i,j, tmp = 0;
    bool swap = false;//加判断条件  减少比较次数
    for ( i = 0; i < len -1; i++)
    {
        for (j = 0; j < len - 1 - i; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;//交换
                swap = true;
            }
        }
        if (!swap)
        {
            return;
        }
    }
}



//显示函数
void show_arr(int *arr, int arr_len)
{
    int  i;
    for(i=0;i<arr_len; i++)
    {
        printf("%d    ", i);
    }
    printf("\n");

}
int main()
{
    
    
    int a[]={4,5,6,72,1,7,9,3,};    //定义了一个9个数的数组,并且初始化
    int len = sizeof(a)/sizeof(a[0]);  //len是数组的大小
    Bubble_sort(a, len);
    show_arr(a, len);
    return 0;

}
View Code



二、选择排序
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定(相同的数值都交换了)
主要思路:
每一次从无序组的数据元素中选出最小的一个元素,存放在无序组的起始位置,无需组的元素减少,有序组的元素增加,直到全部待排序的数据元素排完。
图解:

代码:

/*********************选择排序******************************/
#include<stdio.h>
//#include <assert.h>

//排序函数
void select_sort(int *arr, int len)
{
    //assert(arr != NULL);
    int i, j;
    int tmp;
    int min = 0;
    for (i = 0; i < len; i++)
    {
        int min = i;
        for (j = i + 1; j < len; j++)
        {
            if (arr[j] < arr[min])
            {
                tmp = arr[min];
                arr[min] = arr[j];
                arr[j] = tmp;
            }
        }
    }
}

//显示函数

void show_arr(int *arr, int arr_len)
{
    int  i;
    for(i=0;i<arr_len; i++)
    {
        printf("%d    ", i);
    }
    printf("\n");

}
int main()
{
    int i = 0;         //定义一个i并且赋初值为0,i作为数组的下标
    int j = 0;      //定义j并且赋初值为0,j作为找到最大值时所对应的下标
    
    int a[]={4,5,6,72,1,7,9,3,};    //定义了一个9个数的数组,并且初始化
    int len = sizeof(a)/sizeof(a[0]);  //len是数组的大小
    select_sort(a, len);
    show_arr(a, len);
    return 0;

}
View Code


三、插入排序
直接插入排序:
时间复杂度:无序 O(n^2) 有序O(n)
空间复杂度:O(1)
稳定性:稳定
主要思路:
插入排序是最简单常用的方法,将数组分为两部分,排好序的数列,以及未排序的数列,将未排序的数列中的元素 与排好序的数列进行比较,然后将该元素插入到已排序列的合适位置中。
图解:

代码:

/*********************插入排序******************************/
#include<stdio.h>
//#include <assert.h>

//排序函数
void Insert_sort(int *arr, int len)
{
    //assert(arr != NULL);
    int i, j;
    int tmp=0;
    for (i = 1; i < len; i++)
    {
        tmp = arr[i];
        for (j = i - 1; j >= 0; j--)
        {
            if (arr[j] > tmp)
            {
                arr[j + 1] = arr[j];
            }
            else
            {
                break;
            }
        }
        arr[j + 1] = tmp;
    }
}

//显示函数

void show_arr(int *arr, int arr_len)
{
    int  i;
    for(i=0;i<arr_len; i++)
    {
        printf("%d    ", i);
    }
    printf("\n");

}
int main()
{
    int i = 0;         //定义一个i并且赋初值为0,i作为数组的下标
    int j = 0;      //定义j并且赋初值为0,j作为找到最大值时所对应的下标
    
    int a[]={4,5,6,72,1,7,9,3,};    //定义了一个9个数的数组,并且初始化
    int len = sizeof(a)/sizeof(a[0]);  //len是数组的大小
    Insert_sort(a, len);
    show_arr(a, len);
    return 0;

}
View Code


四、希尔排序:(直接插入的优化)
时间复杂度:O(n^1.5-n^1.5)
空间复杂度:O(1)
稳定性:不稳定
工作原理:
把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
图解:

代码:

void Shell(int *arr, int len,int gap)
{
    assert(arr != NULL);
    int tmp = 0;
    int i, j;
    for (i = gap; i < len; i += gap)
    {
        tmp = arr[i];
        for (j = i - gap; j >= 0; j -= gap)
        {
            if (arr[j] > tmp)
            {
                arr[j + gap] = arr[j];
            }
            else
            {
                break;
            }
        }
        arr[j + gap] = tmp;
    }
}
void Shell_sort(int *arr, int len)
{
    int drr[] = { 5,3,1 };//分组  必须是素数  最后一个必须为1
    int lend = sizeof(drr) / sizeof(drr[0]);
    for (int i = 0; i < lend; i++)
    {
        Shell(arr, len, drr[i]);
    }
}
View Code



五、堆排序
时间复杂度:O(nlog2n)
空间复杂度:O(1)
稳定性:不稳定
主要思路:
1.将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。
2.将其与末尾元素进行交换,此时末尾就是最大值。
3.然后将剩余n -1 个元素重新构造成一个堆,这样会得到n个元素的次小值。
4.如此反复执行,便得到一个有序序列。
图解:

代码:

void Adjust(int *arr,int start,int end)
{
    assert(arr != NULL);
    int tmp = arr[start];
    for(int i = 2*start + 1;i <= end;i = 2*i+1)
    {
        if(i < end && arr[i] < arr[i + 1])//是否有右孩子
        {
            i++;//最大值的下标
        }
        if(arr[i] > tmp)
        {
            arr[start] = arr[i];
            start = i;
        }
        else
        {
            break;
        }
    }
    arr[start] = tmp;
}
void HeapSort(int *arr,int len)
{
    for(int i = (len - 1 - 1)/2;i >= 0;i--)
    {
        Adjust(arr,i,len - 1);
    }
    for(int i = 0;i <= len - 1;i++)
    {
        int tmp = arr[0];
        arr[0] = arr[len - 1 - i];
        arr[len - 1 - i] = tmp;
        Adjust(arr,0,len - 1 - 1 - i);
    }
}
View Code


六、快速排序
时间复杂度:最好情况下:O(nlong2n) 最坏情况下:O(n^2)
空间复杂度:O(nlong2n)
稳定性:不稳定
主要思路:
快速排序是找出一个元素(理论上可以随便找一个)作为基准,然后对数组进行分区操作,使基准左边元素的值都不大于基准值,基准右边的值都不小于基准值,如此作为基准的元素调整到排序后的正确位置。递归快速排序,将其他n - 1 个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正确位置,排序完成。所以快速排序算法的核心算法是分区操作,及如何调整基准的位置以及调整返回基准的最终位置以便分治递归。
图解:

代码:

//快排的递归
int  Partition(int *arr,int left,int right)
{
    int tmp=arr[left];//将第一个给tmp 
    while(left<right)//数组存在  并大于1
    {
        while(left<right&&arr[right]>tmp)//右边的大于tmp时
        {
            right--;
        }
        if(left>=right)//第一次循环完了 
        {
            break;
        }
        else
        {
            arr[left]=arr[right];
        }
        while(left<right&&arr[left]<=tmp)//左边的大于tmp时
        {
            left++;
        }
        if(left>=right)
        {
            break;
        }
        else
        {
            arr[right]=arr[left];
        }
    }
        arr[left]=tmp;
        return left;//返回相遇的那个位置
    }
void Quick(int *arr,int start,int end)
    {
        int par=Partition(arr,start,end);
        if(par>=start+1)
    {
        Quick(arr,start,par-1);//当前面部分有两个以上的数时再次排序
    }
    if(par<end-1)
    {
        Quick(arr,par+1,end);//当后面部分有两个以上的数时再次排序
    }
    }
void Quick_Sort(int  *arr,int len)
    {
        Quick(arr,0,len-1);//调用Quick函数  排序
    }

非递归
void Quick_Sort1(int  *arr,int len)
    {
        int *stack=(int *)malloc(sizeof(int)*len);//定义栈的大小
        assert(stack!=NULL);//断言
        int top=0;//指向栈底
        int left=0;//最左端
        int right=left-1;//指向最右端
        int par=Partition(arr,left,right);
        if(par>left+1)//左边有数时  入栈
        {
            stack[top++]=left;
            stack[top++]=par-1;
        }
        if(par<right-1)//右边有数时
        {
            stack[top++]=par+1;
            stack[top++]=right;
        }
        while(top>0)//出栈
        {
            right=stack[--top];
            left=stack[--top];
            par=Partition(arr,left,right);
            if(par>left+1)
        {
            stack[top++]=left;
            stack[top++]=par-1;
        }
        if(par<right-1)
        {
            stack[top++]=par+1;
            stack[top++]=right;
        }
        }
        free(stack);
        stack=NULL;
    }
快排的三种优化:
1.随机选取基准法
     void Swap(int *arr,int low,int high)
{
    int tmp = arr[low];
    arr[low] = arr[high];
    arr[high] = tmp;
}

void Quick(int *arr,int start,int end)
{
    Swap(arr,start, rand()%(end-start) + start);//每次需要在high这面产生随机值
    int par = Partion(arr,start,end);
    if(par > start+1)//左边是不是有两个数据以上
    {
        Quick(arr,start,par-1);
    }
    if(par < end-1)
    {
        Quick(arr,par+1,end);
    }
}


2.三分取中法
    /*void Swap(int *arr,int low,int high)
{
    int tmp = arr[low];
    arr[low] = arr[high];
    arr[high] = tmp;
}
void Median_of_Three(int *arr,int start,int mid,int end)
{//      arr[mid]<=arr[start]<=arrr[end]
    if(arr[mid] > arr[end])//arr[mid]<=arr[end]
    {
        Swap(arr,mid,end);
    }

    if(arr[start] < arr[mid])//arr[mid]<=arr[start]
    {
        Swap(arr,start,mid);
    }

    if(arr[start] > arr[end])
    {
        Swap(arr,start,end);
    }
}
void Quick(int *arr,int start,int end)
{
    Median_of_Three(arr,start,(end-start)/2+start,end);
    int par = Partion(arr,start,end);
    if(par > start+1)//左边是不是有两个数据以上
    {
        Quick(arr,start,par-1);
    }
    if(par < end-1)
    {
        Quick(arr,par+1,end);
    }
}
3.聚集相同元素法
    void Swap(int *arr,int low,int high)
{
    int tmp = arr[low];
    arr[low] = arr[high];
    arr[high] = tmp;
}
void Focus_Same_Num(int *arr, int low, int par, int high, int *left, int *right)   //优化4
{
    int parLeft_Index = par-1;
    int parRight_Index = par+1;

    for(int i = par-1;i >= low;i--)
    {
        if(arr[i] == arr[par] && i != parLeft_Index)
        {
            Swap(arr,i,parLeft_Index);
            parLeft_Index--;
        }
    }
    *left = parLeft_Index;

    for(int i = par+1;i <= high;i++)
    {
        if(arr[i] == arr[par] && i != parRight_Index)
        {
            Swap(arr,i,parRight_Index);
            parRight_Index++;
        }
    }
    *right = parRight_Index;

}

void Quick(int *arr,int start,int end)
{

    int par = Partion(arr,start,end);
    int par_left = par-1;
    int par_right = par+1;
    Focus_Same_Num(arr,start,par,end,&par_left,&par_right);

    if(par > start+1)//左边是不是有两个数据以上
    {
        Quick(arr,start,par-1);
    }

    if(par < end-1)
    {
        Quick(arr,par+1,end);
    }
}
View Code

七、基数排序
时间复杂度:最好情况:O(d(n+rd)) 最坏情况:O(d(r+n)) r代表关键字的基数 d代表长度 n代表关键字的个数
空间复杂度:O(rd+n)
稳定性:稳定
主要思路:
将整数按位数切割成不同的数字,然后按每个位数分别比较。
将所有待比较数值统一为同样的数位长度,数位较短的数前面补0.然后,从最低位开始,依次进行排序。这样从最低位一直到最高位排完序后,数列就变成一个有序序列。
图解:
第一次入桶:按个位数大小

第二次入桶:按十位数大小

第三次入桶: 按百位数的大小

代码:

typedef struct Node
{
    int data;
    struct Node *next;
}Node, *List;
//运用单链表  
void InitList(List plist)//初始化
{
    assert(plist != NULL);
    if (plist == NULL)
    {
        return;
    }
    plist->next = NULL;
}
static Node *GetNode(int val)
{
    Node *p = (Node *)malloc(sizeof(Node));
    assert(p != NULL);
    p->data = val;
    p->next = NULL;
    return p;
}
bool Insert_tail(List plist, int val)//尾插
{
    Node *p;
    for (p = plist; p->next != NULL; p = p->next)
    {
        ;
    }
    Node *pGet = GetNode(val);
    p->next = pGet;
    return true;
}
bool DeletFirst(List plist, int *rtv)//去掉第一个
{
    assert(plist != NULL);
    Node *p=plist->next;
    if (plist->next == NULL)
    {
        return false;
    }
    if (rtv != NULL)
    {
        *rtv = p->data;
    }
    plist->next = p->next;
    free(p);
    p = NULL;
    return true;
}
int GetMaxBit(int *arr, int len)//获取最大数的位数
{
    int Max =arr[0];
    for (int i = 0; i <= len - 1; i++)
    {
        if (arr[i] > Max)
        {
            Max = arr[i];
        }
    }
    int count = 0;
    while (Max != 0)
    {
        count++;
        Max /= 10;
    }
    return count;
}
int GetNum(int num, int date)//得到num的第date位数字
{
    for (int i = 0; i < date; i++)
    {
        num /= 10;
    }
    return num % 10;
}
void Radix(int *arr, int len, int date)
{
    Node head[10];
    for (int i = 0; i < 10; i++)
    {
        InitList(&head[i]);
    }
    int i;
    int tmp;    
    for(i = 0; i < len; i++)//入桶
    {
        tmp = GetNum(arr[i],date);
        Insert_tail(&head[tmp],arr[i]);
    }   
    i = 0;
    for(int j = 0; j < 10;)//出桶 
    {
        if(DeletFirst(&head[j], &arr[i]))
        {
            i++;//出一桶内的数据
        }
        else
        {
            j++;//一桶内数据出完后
        }

    }
}
void RedixSort(int *arr, int len)
{
    int count = GetMaxBit(arr, len);//进出桶多少次
    for(int i = 0; i < count; i++)
    {
        Radix(arr, len, i);//i表示从右数第0位开始
    }
View Code

八、归并排序
时间复杂度:O(nlong2n)
空间复杂度:O(n)
稳定性:稳定
主要思想:
将待排序序列R[0…n-1]看成是n个长度为1的有序序列,将相邻的有序表成对归并,得到n/2个长度为2的有序表;将这些有序序列再次归并,得到n/4个长度为4的有序序列;如此反复进行下去,最后得到一个长度为n的有序序列。
图解:

代码:

void Mearge(int *arr,int len,int gap)
{
    int *brr = (int *)malloc(sizeof(int)*len);//申请空间
    assert(brr != NULL);
    int i = 0;//从第一个开始  
    int s1 = 0;//指向的一个元素
    int e1 = s1 + gap - 1;//指向第一个归并段的最后一个元素
    int s2 = e1 + 1;//第二段的第一个元素
    int e2 = s2 + gap - 1 < len - 1 ? s2 + gap - 1:len - 1;//判断e2的位置 若超出len 则指向len-1
    while(s2 < len)//当前有两个归并端
    {
        while(s1 <= e1 && s2 <= e2)
        {
            if(arr[s1] < arr[s2])//第一个归并段的开头与第二个归并段的开头比较
            {
                brr[i++] =  arr[s1++];
            }
            else
            {
                brr[i++] = arr[s2++];
            }
        }
        while(s1 <= e1)
        {
            brr[i++] = arr[s1++];
        }
        while(s2 <= e2)
        {
            brr[i++] =arr[s2++];
        }
        s1 = e2 + 1;//第二次重新合并后  重新开始
        e1 = s1 + gap - 1;
        s2 = e1 + 1;
        e2 = s2 + gap - 1 < len ? s2 + gap -1:len - 1;
    }
    while(s1 < len)
    {
        brr[i++] = arr[s1++];
    }
    for(int i = 0;i < len;i++)
    {
        arr[i] = brr[i];
    }
    free(brr);
    }
    void Mearge_Sort(int *arr,int len)
{
    for(int i=1;i<len;i*=2)
    {
        Mearge(arr,len,i);
    }
}
View Code


总结:
1. 当n比较小时,可以直接采用直接插入排序或者选择排序;
2.若数据初始状态基本有序(正序),选择直接插入排序、冒泡排序或者快速排序为宜;
3.若n比较大,则采用时间复杂度为O(nlong2n)的排序算法:快速排序、堆排序或者归并排序;
4.快速排序是目前基于比较的排序中被认为最好的方法,当待排关键字是随机分布时,快速排序的平均时间最短;
5.堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况,所以这两个排序实际运用的时候可以比较一下,选择最优的;但是这两种排序都是不稳定的。
6.若要求排序稳定,则可以选用归并排序,但是实际运用的时候通常可以将它和直接插入排序结合使用,先利用直接插入排序求得较长有序子序列,然后再两两归并。



猜你喜欢

转载自www.cnblogs.com/sd-xyy/p/12903713.html