本文主要记录使用模板函数来编写排序算法,并计算运行时间。
模板函数(Template Function)是一种通用函数,可以在其定义时不指定具体的参数类型,在调用时再根据需要指定具体类型。模板函数可以接受不同类型的参数,并产生可重用性强的代码。
使用模板函数的主要目的是为了提高代码的复用性和灵活性,特别是当需要编写的函数在不同的参数类型下都要执行相同的操作时。通过模板函数可以避免重复编写相似的代码,使程序更加简洁、简单和易于维护。
- 选择排序:
- 选择排序是不稳定的排序,因为其在排序过程中,会改变原本元素的相对位置
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 冒泡排序:
- 冒泡排序是稳定的排序,因为每次排序都是相邻两个元素比较排序,不会改变相等元素的相对位置
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 快速排序:
- 快速排序是不稳定的排序
- 时间复杂度:O(nlogn)
- 空间复杂度:O(1) 因为采用递归,占用了栈空间
- 定义了一个交换函数
- 定义了一个打印函数
- 定义了一个选择排序函数
// 模板函数格式
template <typename T>
xxx函数,如
void fun(T a, T b)
{
xxx
}
// 调用(例如整型)
fun<int>(a,b);
#include <iostream>
using namespace std;
template <typename T>
void my_swap(T &a, T &b) // 交换
{
T temp;
temp = a;
a = b;
b = temp;
}
template <typename T>
void print(T arr[], int len) // 打印函数
{
cout << "排序结果: ";
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
template <typename T>
void select_sort(T arr[], int len) //选择排序
{
for (int i = 0; i < len; i++) {
int max_index = i;
for (int j = i + 1; j < len; j++) {
if (arr[max_index] < arr[j]) {
max_index = j;
}
}
if (max_index != i) {
my_swap<T>(arr[max_index], arr[i]);
}
}
print<T>(arr, len);
}
当我们使用不同数据类型时,就会发现模板函数的好处多多~
int main()
{
int a[5]{ 1,2,3,4,5 };
int len_a = sizeof(a) / sizeof(int);
select_sort<int>(a, len_a);
return 0;
}
int main()
{
double b[5]{ 1.1,2.2,3.3,4.4,5.5 };
int len_b = sizeof(b) / sizeof(double);
select_sort<double>(b, len_b);
return 0;
}
int main()
{
char c[] = "abcde";
int len_c = sizeof(c) / sizeof(char);
select_sort<char>(c, len_c);
return 0;
}
接下来,我们来测试下,冒泡排序,以及稍微改进的冒泡排序的运行时间
template <typename T>
void bubble_sort(T arr[], int len) //冒泡排序
{
for (int i = 0; i < len; i++) {
for (int j = i + 1; j < len; j++) {
if (arr[i] < arr[j]) {
my_swap<T>(arr[i], arr[j]);
}
}
}
print<T>(arr, len);
}
template <typename T>
void bubble_p_sort(T arr[], int len) //冒泡排序(优化)
{
for (int i = 0; i < len; i++) {
bool flag = false;
for (int j = i + 1; j < len; j++) {
if (arr[i] < arr[j]) {
my_swap<T>(arr[i], arr[j]);
flag = true;
}
}
if (!flag) break;
}
print<T>(arr, len);
}
#include <iostream>
#include <chrono> // 测时
#include <random> // 生成随机数
using namespace std;
int main()
{
// 创建随机数引擎
std::random_device rd;
std::mt19937 gen(rd()); // 使用 Mersenne Twister 引擎
// 创建分布对象
std::uniform_int_distribution<int> dist(0, 100);//0-100
int a[100];
for (int i = 0; i < 100; i++) // 生成100个随机数
{
a[i] = dist(gen);// 生成随机整数
}
int len = sizeof(a) / sizeof(a[0]);
auto start1 = std::chrono::high_resolution_clock::now(); // 开始计时
bubble_sort(a, len); // 冒泡排序
auto end1 = std::chrono::high_resolution_clock::now(); // 结束计时
auto t1 = std::chrono::duration_cast<std::chrono::duration<double>> (end1 - start1).count() * 1000;
std::cout << "冒泡排序代码运行时间: " << t1 << " 毫秒" << std::endl;
auto start2 = std::chrono::high_resolution_clock::now(); // 开始计时
bubble_p_sort(a, len); // 冒泡排序(优化)
auto end2 = std::chrono::high_resolution_clock::now(); // 结束计时
auto t2 = std::chrono::duration_cast<std::chrono::duration<double>> (end2 - start2).count() * 1000;
std::cout << "冒泡排序(优化)代码运行时间: " << t2 << " 毫秒" << std::endl;
return 0;
}
我们可以发现,在时间上还是稍微加快一丢丢,原因是当输入数组已经有序时,会提前结束排序。
使用快速排序:
// 从小到大排序
template <typename T>
int Partition(T a[], int low, int high)
{
T base = a[low]; // 把左边的值作为基准值
while (low < high) {
while (low < high && a[high] >= base) high--; // 如果大于base,则向左平移,找小的数
a[low] = a[high]; // 把小的数放到左侧
while (low < high && a[low] <= base) low++; // 如果小于base,则向右平移,找大的数
a[high] = a[low]; // 把大的数放到右侧
}
a[low] = base;
return low;
}
template <typename T>
void qsort(T a[], int low, int high)
{
if (low < high) {
int pos = Partition<T>(a, low, high);
qsort(a, low, pos - 1);
qsort(a, pos + 1, high);
}
}
#include <iostream>
#include <chrono>
#include <random>
using namespace std;
int main()
{
// 创建随机数引擎
std::random_device rd;
std::mt19937 gen(rd()); // 使用 Mersenne Twister 引擎
// 创建分布对象
std::uniform_int_distribution<int> dist(0, 100);//0-100
int a[100];
for (int i = 0; i < 100; i++) // 生成100个随机数
{
a[i] = dist(gen);// 生成随机整数
}
int n = 100;
auto start1 = std::chrono::high_resolution_clock::now(); // 开始计时
qsort<int>(a, 0, n - 1);
auto end1 = std::chrono::high_resolution_clock::now();
auto t1 = std::chrono::duration_cast<std::chrono::duration<double>> (end1 - start1).count() * 1000;
print(a, n);
std::cout << "快速排序代码运行时间: " << t1 << " 毫秒" << std::endl;
return 0;
}
// 从大到小排序
template <typename T>
int Partition(T a[], int low, int high)
{
T base = a[low]; // 把左边的值作为基准值
while (low < high) {
while (low < high && a[high] <= base) high--;
a[low] = a[high]; // 把大的数放到左侧
while (low < high && a[low] >= base) low++;
a[high] = a[low]; // 把小的数放到右侧
}
a[low] = base;
return low;
}
template <typename T>
void qsort(T a[], int low, int high)
{
if (low < high) {
int pos = Partition<T>(a, low, high);
qsort(a, low, pos - 1);
qsort(a, pos + 1, high);
}
}
#include <iostream>
#include <chrono>
#include <random>
using namespace std;
int main()
{
// 创建随机数引擎
std::random_device rd;
std::mt19937 gen(rd()); // 使用 Mersenne Twister 引擎
// 创建分布对象
std::uniform_int_distribution<int> dist(0, 100);//0-100
int a[100];
for (int i = 0; i < 100; i++) // 生成100个随机数
{
a[i] = dist(gen);// 生成随机整数
}
int n = 100;
auto start1 = std::chrono::high_resolution_clock::now(); // 开始计时
qsort<int>(a, 0, n - 1);
auto end1 = std::chrono::high_resolution_clock::now();
auto t1 = std::chrono::duration_cast<std::chrono::duration<double>> (end1 - start1).count() * 1000;
print(a, n);
std::cout << "快速排序代码运行时间: " << t1 << " 毫秒" << std::endl;
return 0;
}
快速排序名不虚传,确实挺快的!同样100个随机数,冒泡排序,选择排序都要10多毫秒,快速排序只需要0.005毫秒左右!