数据结构与算法分析实验14 实现基本排序算法

实现基本排序算法

  • [1. 常用的排序算法简介](#1. 常用的排序算法简介)
  • [2. 上机要求](#2. 上机要求)
  • [3. 上机环境](#3. 上机环境)
  • 4.程序清单(写明运行结果及结果分析)
    • [4.1 程序清单](#4.1 程序清单)
      • [4.1.1 头文件 sort.h 内容如下:](#4.1.1 头文件 sort.h 内容如下:)
      • [4.1.2 实现文件 sort.cpp 内容如下:](#4.1.2 实现文件 sort.cpp 内容如下:)
      • [4.1.3 源文件 main.cpp 内容如下:](#4.1.3 源文件 main.cpp 内容如下:)
    • [4.2 实现展效果示](#4.2 实现展效果示)
  • 5.上机体会

1. 常用的排序算法简介

  1. 冒泡排序
    冒泡排序是一种简单的排序算法,通过重复地遍历要排序的列表,比较相邻的元素并交换它们的位置,直到没有需要交换的元素为止。冒泡排序的时间复杂度为 O(n²),适用于小规模数据。
  2. 选择排序
    选择排序通过每次从未排序的部分中选择最小(或最大)的元素,将其放到已排序部分的末尾。选择排序的时间复杂度为 O(n²),适用于小规模数据。
  3. 插入排序
    插入排序通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序的时间复杂度为 O(n²),适用于小规模或部分有序的数据。
  4. 快速排序
    快速排序是一种分治算法,通过选择一个"基准"元素,将数组分为两部分,一部分小于基准,另一部分大于基准,然后递归地对这两部分进行排序。快速排序的平均时间复杂度为 O(n log n),适用于大规模数据。
  5. 归并排序
    归并排序也是一种分治算法,通过将数组分成两半,分别对它们进行排序,然后将排序后的两半合并。归并排序的时间复杂度为 O(n log n),适用于大规模数据。
  6. 堆排序
    堆排序利用堆这种数据结构进行排序,通过构建最大堆或最小堆,将堆顶元素与末尾元素交换,然后调整堆,重复此过程直到排序完成。堆排序的时间复杂度为 O(n log n),适用于大规模数据。
    ..................

2. 上机要求

编写一个程序,实现排序的相关运算,并完成如下功能:

(1)直接插入排序

(2)希尔排序

(3)直接选择排序

(4)堆排序

(5)冒泡排序

(6)快速排序

(7)二路归并排序

(8)基数排序

3. 上机环境

visual studio 2022

Windows11 家庭版 64位操作系统

4.程序清单(写明运行结果及结果分析)

4.1 程序清单

4.1.1 头文件 sort.h 内容如下:

cpp 复制代码
#pragma once
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
#include<iostream>
//采用从大到小的顺序

typedef int Data;

//交换两个元素(值交换)
void swap(Data* a, Data* b);

//直接插入排序
void Sort_insert(Data* arr,int len);

//希尔排序
void Sort_shell(Data* arr,int len,int* way,int size);
void Sort_jump(Data* arr, int len, int jump);	//shell sort 内置函数

//直接选择排序,每次确定两个元素
void Sort_choose(Data* arr, int len);

//堆排序
void Sort_heap(Data* arr, int len);
void min_down(Data* arr, int index);	//一次遍历 arr 数组 将最小元素放在index中
int Lcd(int index);						//index 节点"左子树"
int Rcd(int index);						//index 节点"右子树"
int Pat(int index);						//index 节点"双亲"

//冒泡排序
void Sort_bubble(Data* arr, int len);

//快速排序
void Sort_quick(Data* arr, int low,int high);

//利用二路归并排序的想法将一个数组排序
void Sort_merge(Data* arr,Data* tmp, int low, int high);
void Merge(Data* arr, Data* tmp, int start, int mid, int end);		//Sort_merge内置函数

//基数排序,需要用链表结构以提升时间,节约空间
void Sort_bucket(Data* arr, int len,int exp);
void distribute_collect(Data* arr, int len,int exp);	//发到筒里去,收回去

typedef struct Node {
	Data data;
	struct Node* next;
}node, * pnode;

void push(node* aim, Data data);
Data pop(node* aim);

4.1.2 实现文件 sort.cpp 内容如下:

cpp 复制代码
#include"sort.h"
#include<math.h>

void swap(Data* a, Data* b) {
	Data c = *a;
	*a = *b;
	*b = c;
}

void Sort_insert(Data* arr, int len){
	for (int i = 1; i < len; i++) {
		int tmp = arr[i];
		for (int j = i-1; j >=0; j--) {
			if (arr[j] < tmp) arr[j + 1] = arr[j]; 
			else { arr[j+1] = tmp; break; }
			if (j == 0)arr[j] = tmp;
		}
	}
}

希尔排序实现:

希尔排序(Shell Sort)是一种基于插入排序的算法,通过将数组分成多个子序列进行排序,逐步减少子序列的长度,最终完成整个数组的排序。以下是希尔排序的实现步骤:

  1. 选择增量序列
    希尔排序的核心是选择一个增量序列(gap sequence),用于将数组分成多个子序列。常见的增量序列有希尔原始序列(n/2, n/4, ..., 1)或更高效的序列如Hibbard序列、Sedgewick序列等。
cpp 复制代码
void Sort_shell(Data* arr, int len, int* way, int size){
	for (int i = 0; i < size; i++) {
		Sort_jump(arr, len, way[i]); 
	}
}
//arr 就是选择的序列
  1. 分组插入排序
    根据选定的增量序列,将数组分成若干子序列,对每个子序列进行插入排序。随着增量逐渐减小,子序列的长度逐渐增加,最终当增量为1时,整个数组被当作一个子序列进行插入排序。
cpp 复制代码
void Sort_jump(Data* arr, int len, int jump){
	for (int i = 1; i < len; i++) {
			int tmp = arr[i];
			for (int j = i - jump; j >= 0; j-=jump) {
				if (arr[j] < tmp) arr[j + jump] = arr[j];
				else { arr[j + jump] = tmp; break; }
				if (j <jump)arr[j] = tmp;
			}
		
	}
}

选择排序

cpp 复制代码
void Sort_choose(Data* arr, int len){
	int maxflag = 0;
	int minflag = 0;
	for (int i = 0; i < len; i++) {
		maxflag = i;
		minflag = len - 1 - i;
		for (int j = i; j <= len - 1 - i; j++) {
			if (arr[maxflag] < arr[j])maxflag = j;
			if (arr[minflag] > arr[j])minflag = j;
		}
		swap(&arr[i], &arr[maxflag]);
		if (i == minflag) minflag = maxflag;		
		swap(&arr[len - 1 - i], &arr[minflag]);
	}
}

堆排序

堆排序通过构建一个最大堆或最小堆,将堆顶元素(最大或最小)与堆的最后一个元素交换,然后调整剩余元素使其重新满足堆的性质,重复这一过程直到所有元素有序。

  1. Sort_heap 函数:这是堆排序的主函数,负责对整个数组进行排序。它从数组的最后一个元素开始,逐个调用 min_down 函数来调整堆。
cpp 复制代码
void Sort_heap(Data* arr, int len){
	for (int i = len - 1; i >= 0; i--) {
		min_down(arr, i);
	}
}
  1. min_down 函数:该函数用于调整堆,确保从指定索引开始的子树满足最小堆的性质。它通过比较父节点和子节点的值,并在必要时交换它们的位置。
cpp 复制代码
void min_down(Data* arr, int index) {
	if (Pat(index) >= 0) {
		int pos = Pat(index);
		while (pos>=-1) {
			if (arr[Lcd(pos)] < arr[pos]&&Lcd(pos)<=index) swap(&arr[Lcd(pos)], &arr[pos]);
			if (arr[Rcd(pos)] < arr[pos]&&Rcd(pos)<=index) swap(&arr[Rcd(pos)], &arr[pos]);
			pos--;
		}
		swap(&arr[index], &arr[0]);
	}
}
  1. Lcd 和 Rcd 函数:这两个函数分别用于计算给定索引的左子节点和右子节点的索引。
cpp 复制代码
int Lcd(int index){
	return 2 * index + 1;
}

int Rcd(int index){
	return 2 * index + 2;
}
  1. Pat 函数:该函数用于计算给定索引的父节点索引。
cpp 复制代码
int Pat(int index){
	return (index - 1) / 2;
}

冒泡排序

cpp 复制代码
void Sort_bubble(Data* arr, int len){
	for (int i = 0; i < len; i++) {
		for (int j = i + 1; j < len; j++) {
			if (arr[i] < arr[j])swap(&arr[i], &arr[j]);
		}
	}
}

快速排序

快速排序是一种高效的排序算法,采用分治法策略。其基本思想是通过选择一个"标杆"元素(通常称为"pivot"),将数组分为两部分:一部分小于标杆,另一部分大于标杆。然后递归地对这两部分进行排序。

cpp 复制代码
void Sort_quick(Data* arr, int low, int high){
	int flag = low;	//标杆元素下标
	if (low == high) return;
	for (int i = flag + 1; i <= high; i++) {
		if (arr[flag] >= arr[i])continue;
		if (arr[flag] < arr[i]) {
			swap(&arr[flag + 1], &arr[i]);
			swap(&arr[flag], &arr[flag + 1]);
			flag++;
		}
	}
	if (flag > low)		Sort_quick(arr, low, flag - 1);
	if (flag < high)	Sort_quick(arr, flag + 1, high);
}

归并排序

Sort_merge 函数是归并排序的递归部分。它将数组 arr 从 start 到 end 的部分分成两个子数组,分别对这两个子数组进行排序,然后调用 Merge 函数将两个有序的子数组合并。

Merge 函数负责将两个有序的子数组合并成一个有序的数组。它使用两个指针 i 和 j 分别指向两个子数组的起始位置,比较两个指针所指向的元素,将较小的元素放入临时数组 tmp 中。最后,将临时数组中的内容复制回原数组 arr。

cpp 复制代码
void Sort_merge(Data* arr, Data* tmp, int start, int end){
	int mid;
	if (start < end) {
		mid = (start + end) / 2;
		Sort_merge(arr, tmp, start, mid);
		Sort_merge(arr, tmp, mid+1, end);
		Merge(arr, tmp, start, mid, end);
	}
}

void Merge(Data* arr, Data* tmp,int start, int mid, int end){
	int move = start; 
	int i = start; 
	int j = mid + 1;
	while (i!=mid+1 && j!= end+1) {
		if (arr[i] > arr[j]) {
			tmp[move++] = arr[i++];
		}
		else {
			tmp[move++] = arr[j++];
		}
	}
	while (i != mid + 1) { tmp[move++] = arr[i++]; }
	while (j != end + 1) { tmp[move++] = arr[j++]; }
	for (i = start; i <= end; i++) {
		arr[i] = tmp[i];
	}
}

桶排序

桶排序(Bucket Sort)是一种分布式排序算法,它将元素分配到有限数量的桶中,然后对每个桶中的元素进行排序,最后将桶中的元素按顺序合并。以下是对代码的详细解析和优化建议。

Sort_bucket函数通过循环调用 distribute_collect 函数,对数组进行多次排序。exp 参数表示排序的轮数,通常与数据的位数相关。

cpp 复制代码
void Sort_bucket(Data* arr, int len, int exp){
	for (int i = 1; i <= exp; i++) {
		//printf("exp=%d: \n", i);
		distribute_collect(arr, len, i);
	}
}

void distribute_collect(Data* arr, int len,int exp){
	pnode bucket = (pnode)malloc(10 * sizeof(node));
	for (int i = 0; i < 10; i++) {
		bucket[i].next = NULL;
	}
	int lab = 0;
	for (int i = len-1; i >=0; i--) {									//对到每个元素	从大到小排序就要从小到大塞进去 反之反之
		lab = arr[i];
		for (int j = 0; j < exp - 1; j++) lab /= 10;
		lab %= 10;														//找到桶的下标
		push(&bucket[lab], arr[i]);										//塞到桶里去
		//printf("push %d to lab %d\n", arr[i],lab);
	}
	//收回来
	int index = 0;
	for (int i = 9; i >= 0; i--) {
		while (bucket[i].next != NULL) {
			arr[index++] = pop(&bucket[i]);
		}
	}
}

void push(node* aim, Data data){
	node* fresh = (node*)malloc(sizeof(node));
	fresh->next = NULL;
	fresh->data = data;
	fresh->next = aim->next;
	aim->next = fresh;
}

Data pop(node* aim){
	pnode tmp = aim->next;
	Data ret = aim->next->data;
	aim->next = tmp->next;
	free(tmp);
	return ret;
}

4.1.3 源文件 main.cpp 内容如下:

cpp 复制代码
#include"sort.h"
//#define size 10000
#define size 3200
#define filename  "testdata.txt"
#define tm 123
//注意,在进行快速排序的时候,由于递归深度限制,对于给出的数据量有一个限制
//对于我自己生成的数据,Sort_quick极端情况为3218个数据。
//在实际操作中,Sort_quick数据量应控制在3200以下以确保运行稳定。
//在多次重复排序过程中,由于在后续重复中数据已经排好顺序,快速排序尚且不占优势。
int main() {
	FILE* fp;
	fopen_s(&fp, filename, "r");
	Data* arr = new Data[size];
	for (int i = 0; i < size; i++) {
		if (fp)fscanf_s(fp, "%6d", &arr[i]);
	}
	clock_t start = clock();
	Data* ret = NULL;
	for (int i = 0; i < tm; i++) {
		//Sort_insert(arr, size);

		//int way[7] = { 203,101,47,23,7,3,1 };
		//Sort_shell(arr, size, way, 7);

		//Sort_choose(arr, size);

		//Sort_quick(arr, 0, size-1);

		//Data* tmp = new Data[size];
		//Sort_merge(arr, tmp, 0, size - 1);

		//Sort_bucket(arr, size, 5);

		//Sort_heap(arr, size);
	}
	clock_t end = clock();
	double during = difftime(end, start) / tm;
	for (int i = 0; i < size; i++) {
		printf_s("%d\n", arr[i]);
	}
	printf_s("用时%.2fms", during);

	return 0;
}

4.2 实现展效果示

取消注释对应的代码段,将size 定义成10000(快速排序除外),对排序时间进行考察,给出一些现象产生的原因。


5.上机体会

我的收获主要包括:在上理论课之前,我对于排序算法的理解尚且不到位,就写了一些相关的排序算法,然而在很多地方都忽视了优化问题,直到理论课上完,才重新审视了代码的不足,实现了可读性的增强和基本原理的改进。同样的,对于堆排序时间为什么这么长也是在理论课之后才找到了答案,这也提醒我,不同的排序算法在不同的情况下具有不同的性能。通过实验,可以比较不同算法在处理相同数据集时的执行时间和效率,从而了解它们的相对性能,通过对关键参数的选取,例如枢轴量,增量数组,也更体会到好的算法应当由良好的数学分析而得到。数据结构也会对对算法性能产生影响:排序算法通常需要使用一些数据结构来辅助操作,如数组、链表等。不同的数据结构对算法的性能也会有影响。

相关推荐
ThE.wHIte.9 分钟前
leetcode 3068. 最大节点价值之和
算法·leetcode·职场和发展
鸡鸭扣15 分钟前
leetcode hot100:十四、解题思路大全:真·大全!
数据结构·python·算法·leetcode·力扣·笔试
Java永无止境29 分钟前
JAVASE:常见的算法
java·开发语言·数据结构·算法·排序算法
帝锦_li35 分钟前
十大排序算法--快速排序
排序算法
猎板PCB厚铜专家大族1 小时前
2025有铜半孔工艺参数设计规范
网络·算法·设计规范
白熊1882 小时前
【图像大模型】AnimateDiff:基于扩散模型的视频生成技术解析与实践指南
人工智能·算法·音视频
2401_876907522 小时前
IEC 60034-30-1标准解析:旋转电机能效分级与全球影响
网络·数据结构·经验分享·科技·学习方法
滴水之功3 小时前
C语言数据结构-链式栈
linux·c语言·数据结构
虾球xz3 小时前
游戏引擎学习第309天:用于重叠检测的网格划分
c++·学习·算法·游戏引擎
蚊子爱喝水3 小时前
Redis7 新增数据结构深度解析:ListPack 的革新与优化
数据结构·redis