一些常用排序算法的实现(C语言版)

一些比较常用的排序算法(C语言)

  • 插入排序
  • 选择排序
  • 归并排序
  • 堆排序
  • 计数排序
  • 快速排序
插入排序
  • 算法分析
  1. 从第2个数开始,与之前第一个数比较,如果小于第一个数,那么将第一个数后移,同时设置临时变量保存第二个数,将第一个数设置为第二个数
  2. 依次类推,当比较到arr[i]时,那么将arr[i]与arr[i-1]个数相比较,如果第i个数小于第i-1个数, 那么将第i-1个数后移,同时,与第i-2个数想比较…
  3. 如果第arr[i-1]个数>arr[i-2]个数,跳出循环,同时将保存的第i个数插入到i-2的位置
  • 算法演示
    一些常用排序算法的实现(C语言版)_第1张图片

  • 算法实现(带主函数)

void InsertSort(int numbers[],int length)
{
     
    int p;  //保存当前的位置
    int tempValue; //保存当前变量
    for (int i = 1;i < length ;i++)
    {
     
        tempValue = numbers[i]; //从第二个元素开始
        p = i - 1; //标记上一个元素的位置
        while (p >= 0 && tempValue < numbers[p]) {
      
        //与上一个数比较,如果上一个数>当前要比较的数,将上一个数后移
            numbers[p + 1] = numbers[p];
            p --;   //再与上一个数的上一个数进行比较
        }
        numbers[p+1] = tempValue;  //如果上一个数<当前要比较的数,则将当前数插入到相应的位置
    }
}

主函数的实现

/*
随机生成一组数,测试使用,当然可以自己手动构建一组数进行测试
*/
#include 
#include 
#include 
void initARandom(int nums[],int length)
{
     
    srand((unsigned)time(NULL));
    for (int i = 0; i < length; ++i) {
     
        nums[i] = rand()%60;
    }
}
/*
打印数组中的值,方便测试
*/
void print_array(int A[],int length,char *print_string){
     
    printf("%s\n", print_string);
    for (int i = 0; i < length ; ++i){
     
        printf("%d\t",A[i]);
    }
    printf("\n");
}

int main(int argc, char const *argv[])
{
     
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    InsertSort(numbers, length);
    print_array(numbers,length,"after sort:");
    return 0;
}

输出结果:

before sort:
38	15	54	18	35	31	51	34	46	
after sort:
15	18	31	34	35	38	46	51	54	
Program ended with exit code: 0
  • 算法时间与空间复杂度:
    最好情况:O(n)
    最坏情况:O(n^2)
    平均: O(n^2)
    空间复杂度:O(1)
    是一种稳定的排序算法
选择排序
  • 算法描述
  1. 查找每组数中的最大值,与最后一位数交换
  2. 依次递减,再次查找最大值,与倒数第二个交换
  3. …直到交换到第一个数字,循环结束
  • 算法演示
    一些常用排序算法的实现(C语言版)_第2张图片
  • 算法实现:
/*
查找最大值,返回index
*/
int findMax(int arr[],int length)
{
     
    int max = arr[0];
    int index = 0;
    for (int i = 0; i < length; i++) {
     
        if (max < arr[i])
        {
     
            max = arr[i];
            index = i;
        }
    }
    return index;
}
void selection_sort(int arr[],int length)
{
     
    int maxPos;
    while (length > 1)
    {
     
        maxPos = findMax(arr, length);
        int temp = arr[length - 1];
        arr[length - 1] = arr[maxPos];
        arr[maxPos] = temp;
        length--;
    }
}

主函数中的实现

int main(int argc, char const *argv[])
{
     
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    selection_sort(numbers, length);
    print_array(numbers,length,"after sort:");
    return 0;
}

测试结果

before sort:
25	16	10	52	51	21	7	59	49	
after sort:
7	10	16	21	25	49	51	52	59	
Program ended with exit code: 0
  • 算法时间与空间复杂度:
    最好情况:O(n^2)
    最坏情况:O(n^2)
    平均:O(n^2)
    空间复杂度:O(1)
    是不稳定排序算法
归并排序
  • 算法分析
    主要分两段:
    第一段是合并算法
    第二段是分治思想递归分解每一段,对每一小段进行排序
    第一段合并算法:假设我们已经有了一个分为两段排好序的数组(例如:[6, 7, 8, 9,0,1,2,3,4])

    1. 首先定义三个指针变量,i,j k,i指针指向第一段[6,7,8,9]中的第一个首元素,j指向第二段[0,1,2,3,4]中的0,k指针指向要输出的数组首地址,[6,7,8,9,0,1,2,3,4]中的6
    2. 通过比较A[i] 与 B[j]的大小,如果A[i] < B[j],那么,k指向A[i],i++,k++,反之,j++,k++,如果i 与 j 其中的一个已经达到了A 或者B的长度减一,那么跳出循环,将另外一个数组中的元素输出到k所指向的数组中,完成合并

    第二段:产生有序的两段数组,为步骤1,步骤2做准备

    1. 产生一个分两段排好序的数组,用递归操作,首先将数组分为两段,A数组和B数组,再对A数组分段,对B数组分段,直到分段数组中左右两段元素个数都为1个,跳出递归
    2. 调用上述合并算法,将左右两段进行合并,完成排序

    *算法演示
    一些常用排序算法的实现(C语言版)_第3张图片

    • 算法实现:
      合并算法:
/*
leftStart:表示数组开始点
rightEnd:表示数组结束点
m:表示要将数组分割的分割点
*/
void merge(int array[],int leftStart,int m,int rightEnd)
{
     
    int LEFT_SIZE = m - leftStart; //计算左边数组的长度
    int RIGHT_SIZE = rightEnd - m + 1; //计算右边数组的长度
    int leftArray[LEFT_SIZE]; //初始化分段左边的数组
    int rightArray[RIGHT_SIZE];//初始化分段右边的数组
    //1.将传入的数组中前leftSize个元素分别输出的leftArray数组中
    for (int i = leftStart ; i < m; i++)
    {
     
        leftArray[i - leftStart] = array[i];
    }

    //2.将m - rightEnd之间的数据输出到rightArray数组中
    for (int i = m; i <= rightEnd; i++)
    {
     
        rightArray[i - m] = array[i];
    }
    int i = 0,j = 0,k = leftStart;
    //将两个数组内容合并,假如leftArray 或者 rightArray中某一个数组已经达到了它的长度,那么跳出循环
    while (i < LEFT_SIZE && j < RIGHT_SIZE)
    {
     
        if (leftArray[i] < rightArray[j])
        {
     
            array[k] = leftArray[i];
            i++;
            k++;
        }else
        {
     
            array[k] = rightArray[j];
            j++;
            k++;
        }
    }
    //如果是rightArray达到了它的长度,则将leftArray剩余的数据输出到输出的数组
    while (i < LEFT_SIZE)
    {
     
        array[k] = leftArray[i];
        k++;
        i++;
    }
    //如果是leftArray达到了它的长度,则将rightArray剩余的数据输出到输出的数组
    while (j < RIGHT_SIZE)
    {
     
          array[k] = rightArray[j];
          k++;
          j++;
    }
}

分段算法:

//分治法思想,切分乱序数组
void mergeSort(int array[],int leftStart,int rightEnd)
{
     
    if (leftStart == rightEnd) return; else {
     
          int m = (leftStart + rightEnd) / 2;//找出中心位置
          mergeSort(array, leftStart, m);//切分左边数组
          mergeSort(array, m + 1, rightEnd);//切分右边数组
          merge(array, leftStart, m + 1, rightEnd);//合并左右两边的数组
    }
}

主函数中的实现:

int main(int argc, char const *argv[])
{
     
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    mergeSort(numbers, 0, length - 1);
    print_array(numbers,length,"after sort:");
    return 0;
}

实现结果:

before sort:
14	49	50	50	11	13	59	51	56	
after sort:
11	13	14	49	50	50	51	56	59	
Program ended with exit code: 0
  • 算法时间复杂度与空间复杂度
    最好情况:O(nlog2n)
    最坏情况:O(nlog2n)
    平均:O(nlog2n)
    空间复杂度:O(n)
    是一种稳定的排序算法
堆排序
  • 算法分析:
    首先要了解一下二叉树的概念:

    • 每一个堆就是一个完全二叉树,堆中的每一个根节点都小于两个孩子子结点称为小顶堆,每一个根节点大于两个孩子结点称为大顶堆
    • 已知第i个孩子结点,那么父结点为(i- 1)/2 向下取整
    • 已知一个父亲结点i:那么左孩子结点为:2i + 1,右孩子结点为2i+2
    • 最后一个叶子结点为:n - 1,n为结点个数
    • 升序排序,要找大顶堆
    • 降序排序,要找小顶堆

    过程:

    1. 升序排序,创建大根堆
    2. 将大根堆的根节点与最后一个叶子结点进行交换
    3. 交换后将最后一个结点剔除(并不是物理剔除),重新构建大根堆(不包含最后一个最大的叶子结点),直到下次构建大根堆的时候当前结点已经大于n,循环结束,排序完成
    • 算法演示
    • 算法实现:
/*
 交换数组中两个元素之间的顺序
*/
void swap(int arr[],int i, int j)
{
     
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;

}
/*
  除该节点外,子结点均为堆时,可以直接调用此方法,此方法是堆排序的最小化单元
*/
void heapify(int tree[],int n,int i)
{
     
    if (i >= n)  //递归出口
    {
     
        return;
    }
    int left = 2 * i + 1;  //第i个结点的左结点
    int right = 2 * i + 2; //第i个结点的右结点
    int max = i;           
    if (left < n && (tree[max] < tree[left])) 
    {
        //当左结点不是叶子结点的时候(因为叶子结点没有子结点了)
        max = left;
    }
    if (right < n && (tree[max] < tree[right]))
    {
     
        max = right;
    }
    //以上两个判断是找最大值,如果最大值不是当前结点,交换使得当前结点最大
    if (max != i)
    {
     
        swap(tree, max, i);
        //递归当前结点,当前结点达到跳出递归的条件,也就是到了叶子结点,跳出递归
        heapify(tree, n, max);
    }
}

以上heapify算法是除了当前传入的结点,子结点已经是堆了,才会调用该方法,那么如果是一个乱序的数组,则需要首先创建大根堆

void build_heap(int tree[],int n)
{
     
    int last_node = n - 1;//找到最后一个叶子结点,自下而上的将所有父亲结点都变为大根堆
    int parent = (last_node - 1) / 2;//找到父亲结点
    for (int i = parent; i >= 0; i--)
    {
     
        heapify(tree, n, i);将每一个结点都变为大根堆
    }
}

排序实现算法

void heap_short(int tree[],int n)
{
     
    build_heap(tree,n); //首先要创建一个大根堆
    int last_node = n - 1; //找到最后一个叶子结点
    for (int i = last_node;i >= 0; i--)
    {
     
        swap(tree, i, 0);//交换根节点与叶子结点,交换后,根节点再是大根堆结点,但是除了最后一个结点,其他结点的子结点均为大根堆结点,所以需要对根节点重新做一次heapify
        heapify(tree, i, 0);
    }

}

主函数中的实现:

int main(int argc, char const *argv[])
{
     
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    heap_short(numbers, length);
    print_array(numbers,length,"after sort:");
    return 0;
}
before sort:
10	47	40	30	51	37	13	25	23	
after sort:
10	13	23	25	30	37	40	47	51	
Program ended with exit code: 0
  • 算法时间与空间复杂度:
    最好情况:O(nlog2n)
    最坏情况: O(nlog2n)
    平均:O(nlog2n)
    空间复杂度:O(1)
计数排序
  • 算法分析:

    1. 首先,需要一个辅助数组cArray,用于存放计数的数目,还需要一个bArray,用于存放排好序的数据
    2. 获取原数据数组中的最大值,已确定辅助数组cArray的长度,长度为最大值+1
    3. 将原数组中第i个元素出现的次数放入到辅助数组cArray对应的下标,例如 原数组中的数据为3,那么就将1放入cArray下标为3的位置cArray[3]=1
    4. 如果有重复,那么就将该位置计数器加1,重复几次,就加几,比如3出现了4次,那么cArray[3]=4
    5. 将原数组中的元素逐个输出到bArray中,没输出一次,cArray对应的数据就要减1,比如已经输出了3,那么 cArray[3] = 4-1
  • 算法演示:
    一些常用排序算法的实现(C语言版)_第4张图片

  • 算法实现:

//首先要获取数组的最大值
int getMax(int nums[],int length)
{
     
    int max = nums[0];
    for (int i = 1; i < length; i++) {
     
        if (nums[i] > max)
        {
     
            max = nums[i];
        }
    }
    return max;
}
//计数方法
void counting(int numbers[],int bArray[],int k,int length)
{
     
    //找出最大值,确认在计数排序数组中的位置
    int cArray[k + 1];
    for (int i = 0; i <  k + 1; i++)
    {
     
        cArray[i] = 0;
    }
    //计数排序核心,记录每个数据出现的次数,在cArray中对应的位置做+1操作
    for (int i = 0; i < length; i++) {
     
        cArray[numbers[i]] += 1;
    }
    //将cArray中每一项与前一项做加法,得到i(这里的i其实是原数组中的元素) 元素在即将要输出数组中的位置
    for (int i = 1 ;i < k + 1; i++)
    {
     
        cArray[i] += cArray[i - 1];
    }
    //输出到B数组
    for (int i = 0; i < length ; i++)
    {
     
        //输出一次,那么久相应的要在c数组中对应的位置减一
        cArray[numbers[i]] --;
        bArray[cArray[numbers[i]]] = numbers[i];
    }
    numbers

}
//排序的实现
void counting_sort(int array[],int bArray[],int length)
{
     
    int max = getMax(array, length);
    counting(array, bArray, max, length);
}

在主函数中的实现:

int main(int argc, char const *argv[])
{
     
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    int bArray[length];
    counting_sort(numbers,bArray, length);
    print_array(bArray,length,"after sort:");
    return 0;
  • 输出结果
before sort:
6	21	14	32	2	21	33	34	25	
after sort:
2	6	14	21	21	25	32	33	34	
Program ended with exit code: 0
  • 算法时间与空间复杂度分析:
    最好情况:O(n + k)
    最坏情况:O(n + k)
    平均:O(n + k)
    空间复杂度:O(n+k)
    计数排序是一个稳定的排序
快速排序
  • 算法分析:
    在快排中,还是用用到分治的思想,将问题最小化到一个子问题,然后通过递归到全部问题。
    1. 首先,我们要找一个basicKey,也叫pivotKey,作为左右指针比较的基准值,从最右边的指针开始,依次向左移动,直到出现一个比pivotKey小的值,将a[right]赋值给a[left],然后移动左指针left,找到一个比pivokey大的值,将a[left]赋值给a[right],循环直到left=right,此时返回left与right所处的值为整个数组的中心点
    2. 将pivokey赋值给a[left]
    3. 以返回的枢值为中心,分别对左右两个子数组进行步骤一,直到数组中的元素为1,此时,跳出递归
    4. 整个循环结束,排序完成
  • 算法演示图:
    一些常用排序算法的实现(C语言版)_第5张图片
  • 算法实现过程:
//返回枢值(中心值,也就是left与right指针重合的地方)
int partition(int array[],int left,int right)
{
     
    int key = array[left];// 将第1个作为关键字
    while (left < right) {
     // 两端交替向中间扫描
        // 移动最右边的指针,找到比pivotKey值小的值
        while (left < right && array[right] >= key) --right;
        array[left] = array[right];// 将比关键字小的移动到左边
        // 移动左指针,找到比pivotKey大的值
        while (left < right && array[left] <= key) ++left;
        array[right] = array[left];// 将比关键字大的移动到右边
    }
    array[left] = key;// 此时left就是一趟快速排序后的关键字所在的位置
    return left;
}
//排序算法
void quickShort(int array[],int left,int right)
{
     
    if (left < right)
    {
     
        int mid = partition(array,left,right);
        quickShort(array, left, mid - 1); //递归对左子数组排序
        quickShort(array, mid + 1, right);//递归对右子数组排序
    }
}

主函数中的实现

int main(int argc, char const *argv[])
{
     
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    quickShort(numbers, 0, length - 1);
    print_array(numbers,length,"after sort:");
    return 0;
}
  • 输出结果:
int main(int argc, char const *argv[])
{
     
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    quickShort(numbers, 0, length - 1);
    print_array(numbers,length,"after sort:");
    return 0;
}
  • 时间复杂度与空间复杂度:
    最好:O(nlog2n)
    最坏:O(n^2)
    平均:O(nlog2n)
    空间复杂度:O(nlog2n)
    快速排序是不稳定排序

你可能感兴趣的:(工作总结)