【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】

简介: 快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!

目录😋

任务描述

相关知识

1. 快速排序算法的基本原理

2. 快速排序算法步骤

3. 代码示例(以 C++ 为例)

4. 时间复杂度和空间复杂度

测试说明

通关代码

测试结果


任务描述

本关任务:实现快速排序算法

相关知识

为了完成本关任务,你需要掌握:

  1. 快速排序算法的基本原理
  2. 快速排序算法步骤
  3. 代码示例(以 C++ 为例)
  4. 时间复杂度和空间复杂度

1. 快速排序算法的基本原理

快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。

2. 快速排序算法步骤

  • 选择基准元素:可以有多种选择基准元素的方式。常见的有选择数组的第一个元素、最后一个元素或者中间元素等。例如,在简单的实现中,常常选择数组的第一个元素作为基准元素。
  • 划分操作(Partition)
  • 设定两个指针,一个从数组的左边开始(通常称为左指针),一个从数组的右边开始(称为右指针)。
  • 左指针向右移动,直到找到一个大于基准元素的元素;同时,右指针向左移动,直到找到一个小于基准元素的元素。
  • 当左指针和右指针都停止移动后,如果左指针小于右指针,就交换它们所指向的元素。
  • 持续进行上述操作,直到左指针和右指针相遇或者交叉。此时,将基准元素与左指针(此时左指针和右指针相遇的位置)所指向的元素交换。这样就完成了一次划分,使得基准元素左边的元素都小于等于它,右边的元素都大于等于它。
  • 递归排序:对划分后的两部分子数组(基准元素左边的和右边的)分别进行快速排序。这个过程是递归的,即对每个子数组重复选择基准元素、划分和递归排序的步骤,直到子数组的长度为 1 或者 0(这种情况下子数组已经是有序的)。

3. 代码示例(以 C++ 为例)

#include <iostream>
#include <vector>
using namespace std;
// 划分函数
int partition(vector<int>& arr, int low, int high) {
    int pivot = arr[low];
    int i = low + 1;
    int j = high;
    while (true) {
        while (i <= j && arr[i] <= pivot) i++;
        while (i <= j && arr[j] > pivot) j--;
        if (i > j) break;
        swap(arr[i], arr[j]);
    }
    swap(arr[low], arr[j]);
    return j;
}
// 快速排序函数
void quickSort(vector<int>& arr, int low, int high) {
    if (low < high) {
        int pivotIndex = partition(arr, low, high);
        quickSort(arr, low, pivotIndex - 1);
        quickSort(arr, pivotIndex + 1, high);
    }
}
image.gif

在上述代码中:

  • partition函数实现了划分操作。它以arr[low]作为基准元素,通过两个while循环和指针ij的移动来找到需要交换的元素,最后交换基准元素和j所指元素,返回划分后的基准元素位置。
  • quickSort函数是快速排序的主函数,它首先调用partition函数进行划分,然后递归地对划分后的两部分子数组进行排序。

4. 时间复杂度和空间复杂度

  • 时间复杂度
  • 在最好情况下,每次划分都能将数组均匀地分成两部分,时间复杂度为 ,其中 是数组中的元素个数。
  • 在最坏情况下,例如数组已经是有序的(选择第一个元素作为基准元素),每次划分得到的两个子数组长度分别为 ,时间复杂度为
  • 平均时间复杂度是
  • 空间复杂度:快速排序是递归实现的,需要栈空间来保存递归调用的信息。在最好情况下,空间复杂度为 ,因为递归树的高度为。在最坏情况下,空间复杂度为 ,例如数组已经是有序的情况。平均空间复杂度是

测试说明

平台会对你编写的代码进行测试:

测试输入:(第一行是元素个数,第二行是待排序的原始关键字数据。)

10
6 8 7 9 0 1 3 2 4 5
image.gif

预期输出:

排序前:6 8 7 9 0 1 3 2 4 5 
第1次划分:  5  4  2  3  0  1  6  9  7  8
第2次划分:  1  4  2  3  0  5
第3次划分:  0  1  2  3  4
第4次划分:        2  3  4
第5次划分:           3  4
第6次划分:                       8  7  9
第7次划分:                       7  8
排序后:0 1 2 3 4 5 6 7 8 9
image.gif

开始你的任务吧,祝你成功!


通关代码

#include <malloc.h>
#include <stdio.h>
#define MAXL 100     //最大长度
typedef int KeyType; //定义关键字类型为int
typedef char InfoType;
typedef struct {
  KeyType key;   //关键字项
  InfoType data; //其他数据项,类型为InfoType
} RecType;       //查找元素的类型
void CreateList(RecType R[], KeyType keys[], int n) //创建顺序表
{
  for (int i = 0; i < n; i++) // R[0..n-1]存放排序记录
    R[i].key = keys[i];
}
void DispList(RecType R[], int n) //输出顺序表
{
  for (int i = 0; i < n; i++)
    printf("%d ", R[i].key);
  printf("\n");
}
//显示一趟划分后的结果
void disppart(RecType R[], int s, int t) {
  /********** Begin *********/
  for (int i = 0; i < s; i++)
    printf("    ");
  for (int i = s; i <= t; i++)
    printf("%3d ", R[i].key);
  printf("\n");
  /********** End **********/
}
//一趟划分
int partition(RecType R[], int s, int t) {
  /********** Begin *********/
  KeyType pivot = R[s].key; // 从 RecType 中提取 key 字段
  while (s < t) {
    while (s < t && R[t].key >= pivot)
      t--;
    R[s] = R[t];
    while (s < t && R[s].key <= pivot)
      s++;
    R[t] = R[s];
  }
  R[s].key = pivot; // 将 pivot 的值赋回 R[s].key
  return s;
  /********** End **********/
}
//对R[s..t]的元素进行递增快速排序
void QuickSort(RecType R[], int s, int t, int *count) {
  /********** Begin *********/
  int pivotpos;
  if (s < t) {
    (*count)++;                      // 增加划分次数
    printf("第%d次划分:", *count); // 输出划分次数提示信息
    pivotpos = partition(R, s, t);
    disppart(R, s, t);
    QuickSort(R, s, pivotpos - 1, count);
    QuickSort(R, pivotpos + 1, t, count);
  }
  /********** End **********/
}
int main() {
  /********** Begin *********/
  int n;
  scanf("%d", &n);
  KeyType keys[MAXL];
  RecType R[MAXL];
  for (int i = 0; i < n; i++)
    scanf("%d", &keys[i]);
  CreateList(R, keys, n);
  printf("排序前:");
  DispList(R, n);
  int count = 0; // 初始化划分次数
  QuickSort(R, 0, n - 1, &count);
  printf("排序后:");
  DispList(R, n);
  /********** End **********/
  return 0;
}

image.gif


测试结果

image.gif

image.gif

目录
相关文章
|
18小时前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
30 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
18小时前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
97 63
|
18小时前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
32 18
|
18小时前
|
存储 人工智能 算法
【C++数据结构——图】最短路径(头歌教学实验平台习题) 【合集】
任务描述 本关任务:编写一个程序,利用Dijkstra算法,实现带权有向图的最短路径。 相关知识 为了完成本关任务,你需要掌握:Dijkst本关任务:编写一个程序,利用Dijkstra算法,实现带权有向图的最短路径。为了完成本关任务,你需要掌握:Dijkstra算法。带权有向图:该图对应的二维数组如下所示:Dijkstra算法:Dijkstra算法是指给定一个带权有向图G与源点v,求从v到G中其他顶点的最短路径。Dijkstra算法的具体步骤如下:(1)初始时,S只包含源点,即S={v},v的距离为0。
32 15
|
18小时前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
25 10
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
67 2
|
2月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
120 5
|
2月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
124 4
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
165 4
|
3月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
37 4
下一篇
开通oss服务