【数据结构】快速排序,归并排序

简介: 【数据结构】快速排序,归并排序

快速排序

1.hoare版本

根据动图的演示,整理的思路如下,

1.定义left,right,key。key默认是左边第一个元素,像两个指针,左边找比key大的,右边找比k小的,找到的话,交换二者,往返这个过程,当left与right相遇时,交换key和此时相遇的值.

#include<stdio.h>
void swap(int*p,int*q)
{
  int tmp = *p;
  *p = *q;
  *q = tmp;
}
int PartSort1(int* a, int left, int right)
{
  int keyi =left;
  while (left < right)
  {
    while (left<right && a[right]>=a[keyi])
    {
      right--;
    }
    while (left<right && a[left]<= a[keyi])
    {
      left++;
    }
    swap(&a[left],&a[right]);
  }
  swap(&a[keyi], &a[left]);
  return left;
}
int main()
{
  int arr[] = { 6,1,2,7,9,3,4,5,10,8 };
  PartSort1(arr, 0, 9);
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", arr[i]);
  }
}

单趟下来,6出现在正确的位置。

1.为什么大循环是left<right?

当两个小人走到一块去的时候,我们应该交换key位置的值,和相遇时候的值了,而不是让他们两个岔开.

2.为什么在小循环中要加left<right?

假如说数组是10,10,10,10,10,10,10,10,10

在小循环中一直找小,找不到就会越界.

3.return的值有什么用?

return的值相当于分了界,然后就可以分别对子区间使用快排了.

针对每个子区间,使用快排

#include<stdio.h>
void swap(int*p,int*q)
{
  int tmp = *p;
  *p = *q;
  *q = tmp;
}
int PartSort1(int* a, int left, int right)
{
  int keyi =left;
  while (left < right)
  {
    while (left<right && a[right]>=a[keyi])
    {
      right--;
    }
    while (left<right && a[left]<= a[keyi])
    {
      left++;
    }
    swap(&a[left],&a[right]);
  }
  swap(&a[keyi], &a[left]);
  return left;
}
void QuickSort(int* a, int begin, int end)
{
  if (begin >= end)
    return;
  int mid=PartSort1(a,begin,end);
  QuickSort(a,begin, mid - 1);
  QuickSort(a,mid+1,end);
}
int main()
{
  int arr[] = { 6,1,2,7,9,3,4,5,10,8 };
  QuickSort(arr, 0, 9);
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", arr[i]);
  }
}

递归结束条件,如果有两个数据的话还能排一次,如果只有一个数据的话就不用排了
1.为什么要右边先走,左边再走,为啥相遇的值一定比key小或者等于key?
情况1:右边找小,找不到小,一直往左走,与key碰面,相遇的值为key;
情况2:右边找到了小,停在那里,左边找大,一直找不到大,相遇点就停在了比key小的那里
情况3:交换值之后,右边一直找不到小,一直走,相遇点就是左边刚交换完,还没有动的比key小的值.
情况4:交换值之后,右边继续移动,找到小停在那,左边找不到大,相遇点就是比key小的.

2.挖坑法

1.创建临时变量key保存最左侧坑位的值,右边找小找到小之后,将找到的小值填到左边的坑位去,这里变成坑位.左边找大,找到大之后,将该值填入右侧的坑位,依次循环,相遇之后,将key放到相遇点

#include<stdio.h>
void swap(int*p,int*q)
{
  int tmp = *p;
  *p = *q;
  *q = tmp;
}
int PartSort2(int* a, int left, int right)
{
  int key = a[left];
  int pole = left;
  while (left < right)
  {
    while (left < right && a[right] >=key)
    {
      right--;
    }
    a[pole] = a[right];
    pole = right;
    
    while (left < right && a[left] <= key)
    {
      left++;
    }
    a[pole] = a[left];
    pole = left;
    
  }
  a[left] = key;
  return left;
}
void QuickSort(int* a, int begin, int end)
{
  if (begin >= end)
    return;
  int mid=PartSort2(a,begin,end);
  QuickSort(a,begin, mid - 1);
  QuickSort(a,mid+1,end);
}
int main()
{
  int arr[] = { 6,1,2,7,9,3,4,5,10,8 };
  QuickSort(arr, 0, 9);
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", arr[i]);
  }
}

3.前后指针法

前后指针法

#include<stdio.h>
void swap(int*p,int*q)
{
  int tmp = *p;
  *p = *q;
  *q = tmp;
}
int PartSort3(int* a, int left, int right)
{
  int keyi = left;
  int cur = left + 1;
  int prev = left;
  while (cur<=right)
  {
    if (a[cur] < a[keyi])
    {
      prev++;
      swap(&a[cur], &a[prev]);
    }
    cur++;
    }
  swap(&a[keyi], &a[prev]);
  return prev;
}
void QuickSort(int* a, int begin, int end)
{
  if (begin >= end)
    return;
  int mid=PartSort3(a,begin,end);
  QuickSort(a,begin, mid - 1);
  QuickSort(a,mid+1,end);
}
int main()
{
  int arr[] = { 6,1,2,7,9,3,4,5,10,8 };
  QuickSort(arr, 0, 9);
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", arr[i]);
  }
}

4.快速排序非递归版

采用非递归代替递归分割步骤,当区间只有一个值时,将不在入栈.

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
void swap(int*p,int*q)
{
  int tmp = *p;
  *p = *q;
  *q = tmp;
}
typedef struct Stack//定义一个栈的结构体变量 
{
  int* a;
  int top; // 栈顶
  int capacity; // 容量
}Stack;
void StackInit(Stack* ps)
{
  assert(ps);//断言,防止为空指针
  ps->a = NULL;//所指向的地址为空
  ps->capacity = ps->top = 0;//容量和栈中元素个数均为0
}
void StackPush(Stack* ps, int data)
{
  assert(ps);
  if (ps->capacity == ps->top)//如果栈中的元素个数等于栈的容量时考虑扩容,
  {
    int newcapcity = ps->capacity == 0 ? 4 : ps->capacity * 2;//如果刚开始时都等于0,就先给4个空间大小,后面如果满的话,容量扩大1倍
    int* newnode = (int*)realloc(ps->a, sizeof(int) * newcapcity);//申请空间,将申请好的空间首地址传给newnode指针
    assert(newnode);//断言,防止malloc失败
    ps->a = newnode;//将newnode保存的申请空间的首地址传给ps->a,让ps->a指向创建好的空间
    ps->capacity = newcapcity;//容量大小更新为新容量大小
  }
  ps->a[ps->top] = data;//像存数组一样存数据
  ps->top++;//指向下一个
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps)
{
  assert(ps);
  return ps->top == 0;//ps->top为栈中元素个数.==0栈中无元素,无元素要返回1, 无元素ps->t0p==0,这个表达式结果是1,返回1;
}
// 出栈
void StackPop(Stack* ps)
{
  assert(ps);
  assert(!StackEmpty(ps));//防止栈内无元素,继续出栈
  ps->top--;
}
// 获取栈顶元素
int StackTop(Stack* ps)
{
  assert(ps);
  assert(!StackEmpty(ps));
  return ps->a[ps->top - 1];//ps->top为栈中元素个数,由于数组下标是从0开始,所以栈顶元素下标为ps->top-1;
}
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
  assert(ps);
  return ps->top;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
  assert(ps);
  free(ps->a);//free掉动态申请的内存
  ps->a = NULL;//防止野指针
  ps->capacity = ps->top = 0;//容量和栈中元素个数置为0
}
int PartSort1(int* a, int left, int right)
{
  int keyi =left;
  while (left < right)
  {
    while (left<right && a[right]>=a[keyi])
    {
      right--;
    }
    while (left<right && a[left]<= a[keyi])
    {
      left++;
    }
    swap(&a[left],&a[right]);
  }
  swap(&a[keyi], &a[left]);
  return left;
}
void QuickSort(int* a, int begin, int end)
{
  Stack st;
  StackInit(&st);
  StackPush(&st,end);
  StackPush(&st,begin);
  while (!StackEmpty(&st))
  {
    
    int left = StackTop(&st);
      StackPop(&st);
    int right = StackTop(&st);
      StackPop(&st);
    int mid = PartSort1(a, left, right);
    if (mid + 1 < right)
    {
      StackPush(&st,right);
      StackPush(&st,mid+1);
    }
    if (left < mid-1)
    {
      StackPush(&st,mid-1);
      StackPush(&st,left);
    }
  }
StackDestroy(&st);
}
int main()
{
  int arr[] = { 6,1,2,7,9,3,4,5,10,8 };
  QuickSort(arr, 0, 9);
  for (int i = 0; i < 10; i++)
  {
    printf("%d ",arr[i]);
  }
}

归并排序

基本思想:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include<string.h>
void _MergeSort(int* a, int begin, int end, int* tmp)
{
  if (begin >= end)
    return;
  int mid = (begin + end) / 2;
  _MergeSort(a, begin,mid, tmp);
  _MergeSort(a, mid+1, end, tmp);
  int begin1 = begin;
  int end1 = mid;
  int begin2 = mid + 1;
  int end2 = end;
  int j = begin;
  while (begin1 <= end1 && begin2 <= end2)
  {
    if (a[begin1] < a[begin2])
    {
      tmp[j++] = a[begin1++];
    }
    if (a[begin2] < a[begin1])
    {
      tmp[j++] = a[begin2++];
    }
}
  while (begin1 <= end1)
  {
    tmp[j++] = a[begin1++];
  }
  while (begin2 <= end2)
  {
    tmp[j++] = a[begin2++];
  }
  memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
  
}
//归并排序
void MergeSort(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  _MergeSort(a, 0, n - 1, tmp);
  free(tmp);
  
}
int main()
{
  int arr[] = { 6,1,2,7,9,3,4,5,10,8 };
  MergeSort(arr, 10);
  for (int i = 0; i < 10; i++)
  {
    printf("%d ",arr[i]);
  }
}

0-0,1-1return回0-1,

2-2return回0-2

归并排序非递归版

void MergeSortNonR(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  if (tmp == NULL)
  {
    perror("malloc fail");
  }
  int gap = 1;
  while (gap<n)
  {
    int j = 0;
    for (int i = 0; i < n; i += 2 * gap)
    {
      int begin1 = i;
      int end1 = i + gap - 1;
      int begin2 = i + gap;
      int end2 = i + 2 * gap - 1;
      while (begin1 <= end1 && begin2 <= end2)
      {
        if (a[begin1] < a[begin2])
        {
          tmp[j++] = a[begin1++];
        }
        if (a[begin2] < a[begin1])
        {
          tmp[j++] = a[begin2++];
        }
      }
      while (begin1 <= end1)
      {
        tmp[j++] = a[begin1++];
      }
      while (begin2 <= end2)
      {
        tmp[j++] = a[begin2++];
      }
    }
    memcpy(a, tmp, sizeof(int) * n);
    gap *= 2;
  }
}
int main()
{
  int arr[] = { 6,1,2,7,9,3,4,5 };
  MergeSortNonR(arr, 8);
  for (int i = 0; i < 8; i++)
  {
    printf("%d ",arr[i]);
  }
}

gap=1,两个两个排序,然后整体拷贝回去.

gap=2,四个四个排序,然后整体拷贝回去.

gap=8 八个排序,然后整体拷贝回去.

int main()
{
  int arr[] = { 6,1,2,7,9,3,4,5,10 };
  MergeSortNonR(arr, 9);
  for (int i = 0; i < 9; i++)
  {
    printf("%d ",arr[i]);
  }
}

我们换成9个数据,发现程序崩溃.

void MergeSortNonR(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  if (tmp == NULL)
  {
    perror("malloc fail");
  }
  int gap = 1;
  while (gap<n)
  {
    int j = 0;
    for (int i = 0; i < n; i += 2 * gap)
    {
      int begin1 = i;
      int end1 = i + gap - 1;
      int begin2 = i + gap;
      int end2 = i + 2 * gap - 1;
      printf("gap=%d [%d,%d][%d,%d]\n",gap, begin1, end1, begin2, end2);
      while (begin1 <= end1 && begin2 <= end2)
      {
        if (a[begin1] < a[begin2])
        {
          tmp[j++] = a[begin1++];
        }
        if (a[begin2] < a[begin1])
        {
          tmp[j++] = a[begin2++];
        }
      }
      while (begin1 <= end1)
      {
        tmp[j++] = a[begin1++];
      }
      while (begin2 <= end2)
      {
        tmp[j++] = a[begin2++];
      }
    }
    memcpy(a, tmp, sizeof(int) * n);
    gap *= 2;
  }
}

修正边界

void MergeSortNonR(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  if (tmp == NULL)
  {
    perror("malloc fail");
  }
  int gap = 1;
  while (gap<n)
  {
    int j = 0;
    for (int i = 0; i < n; i += 2 * gap)
    {
      int begin1 = i;
      int end1 = i + gap - 1;
      int begin2 = i + gap;
      int end2 = i + 2 * gap - 1;
      if (end1 >= n || begin2 >= n)
      {
        break;
      }
      if (end2 >= n)
      {
        end2 = n - 1;
      }
      
      while (begin1 <= end1 && begin2 <= end2)
      {
        if (a[begin1] < a[begin2])
        {
          tmp[j++] = a[begin1++];
        }
        if (a[begin2] < a[begin1])
        {
          tmp[j++] = a[begin2++];
        }
      }
      while (begin1 <= end1)
      {
        tmp[j++] = a[begin1++];
      }
      while (begin2 <= end2)
      {
        tmp[j++] = a[begin2++];
      }
      memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
    }
    
    gap *= 2;
  }
}
int main()
{
  int arr[] = { 6,1,2,7,9,3,4,5,10 };
  MergeSortNonR(arr, 9);
  for (int i = 0; i < 9; i++)
  {
    printf("%d ",arr[i]);
  }
}

1.修改边界后往回拷贝的就不是n了,而是这个end2 - i + 1

2.如果是9个数据的时候,最后一个数据在gap=8中才开始排序

目录
相关文章
|
7月前
|
算法 搜索推荐
快速排序-数据结构与算法
快速排序(Quick Sort)是一种基于分治法的高效排序算法。其核心思想是通过选择基准(pivot),将数组划分为左右两部分,使得左侧元素均小于基准,右侧元素均大于基准,然后递归地对左右两部分进行排序。时间复杂度平均为 O(n log n),最坏情况下为 O(n²)(如数组已有序)。空间复杂度为 O(1),属于原地排序,但稳定性不佳。 实现步骤包括编写 `partition` 核心逻辑、递归调用的 `quickSort` 和辅助函数 `swap`。优化方法有随机化基准和三数取中法,以减少最坏情况的发生。
454 13
|
存储 搜索推荐 算法
【初阶数据结构篇】归并排序和计数排序(总结篇)
归并排序(MERGE-SORT)是建⽴在归并操作上的⼀种有效的排序算法,该算法是采⽤分治法(Divide andConquer)的⼀个⾮常典型的应⽤。
155 0
|
10月前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
10月前
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
234 10
|
10月前
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
225 7
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
360 1
【初阶数据结构】打破递归束缚:掌握非递归版快速排序与归并排序
【初阶数据结构】打破递归束缚:掌握非递归版快速排序与归并排序
140 4
|
算法
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
|
搜索推荐
【数据结构常见七大排序(三)上】—交换排序篇【冒泡排序】And【快速排序】
【数据结构常见七大排序(三)上】—交换排序篇【冒泡排序】And【快速排序】
【数据结构常见七大排序(三)上】—交换排序篇【冒泡排序】And【快速排序】
|
存储 算法 搜索推荐
【初阶数据结构篇】冒泡排序和快速排序(中篇)
与直接插入排序法相比,比较次数一致,但冒泡排序的交换需要执行三次,而直接插入排序因为使用了tmp临时变量存储要插入的数据,只用执行一次,所以直接插入排序法效率明显更高。
88 1