南邮数据结构实验报告四----各种排序算法
一、各类算法
(一)简单选择排序
简单选择排序(Straightforward Selection Sort)是一种简单直观的比较排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。以下是对简单选择排序的详细介绍:
一、算法原理
简单选择排序的主要操作是选择和交换。在每一轮选择中,算法遍历整个数据区间,选出最小(或最大)的元素,然后与序列的起始位置元素交换。这样,每一轮选择后,序列的有序部分增加一个元素,无序部分减少一个元素。重复这个过程,直到整个序列变得有序。
二、算法步骤
划分区间:将数组分为两个区间,左边为已排序区间,右边为未排序区间。初始时,已排序区间仅包含数组的第一个元素。
选择最小元素:从未排序区间中找到最小元素。
交换元素:将找到的最小元素与未排序区间的第一个元素交换位置,这样最小元素就加入到了已排序区间。
缩小未排序区间:将未排序区间的第一个元素排除在外,剩余部分仍作为未排序区间。
重复步骤:重复步骤2到4,直到未排序区间为空,此时整个数组变为有序。
稳定性:简单选择排序是不稳定的排序算法,因为在交换元素的过程中可能会改变相等元素的相对顺序。
适用性:由于其时间复杂度较高,简单选择排序不适用于大规模数据集。但对于小规模数据集或者几乎已排序的数据集,简单选择排序是一个不错的选择。
//简单选择排序
void SelectSort(List* list) {
int small, i, j;
// 要排序 n-1 次
for (i = 0; i < list->n - 1; i++) {
small = i; //标记每次第一位元素下标
for (j = i + 1; j < list->n; j++) {
if (list->D[j].key < list->D[small].key) { //按非递减顺序比较大小
small = j;
}
}
if (small != i) {
swap(list->D[i], list->D[small]);
}
}
}
(二)直接插入排序
直接插入排序(Straight Insertion Sort)是一种简单的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
以下是直接插入排序的步骤:
从第一个元素开始,该元素可以认为已经被排序;
取出下一个元素,在已经排序的元素序列中从后向前扫描;
如果该元素(已排序)大于新元素,将该元素移到下一位置;
重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置;
将新元素插入到该位置后;
重复步骤 2~5;
重复步骤2~6,直到所有元素均排序完毕。
//直接插入排序
void InsertSort(List* list) {
int i, j;
//i标识待插入元素下标
for (i = 1; i < list->n; i++) {
Entry insertItem = list->D[i]; //标记每次第一位元素
for (j = i - 1; j >= 0; j--) {
//不断将有序序列中元素向后移动,为待插入元素空出一个位置
if (insertItem.key < list->D[j].key) {
list->D[j + 1] = list->D[j];
}
else break;
}
list->D[j + 1] = insertItem; //待插入元素有序存放至有序序列中
}
}
(三)冒泡排序
冒泡排序是一种简单的排序算法,通过重复遍历要排序的数列,比较相邻的两个元素并交换它们(如果需要)来工作,直到没有更多的交换需要进行,这时数列便完全有序。
冒泡排序的基本思想是通过比较相邻元素的值,将较大的元素逐渐移向数列的一端,就像水中的气泡上升一样。具体来说,算法从数列的第一个元素开始,依次比较相邻的两个元素,如果前一个元素比后一个元素大,则交换它们的位置[1]。这样经过第一轮比较后,最大的元素会被移动到数列的末尾。之后算法会对剩下的未排序部分重复同样的步骤,每一轮结束后都能确定一个元素的正确位置[2]。
在最坏的情况下(即数列完全逆序),冒泡排序需要进行(n-1)轮比较,每轮比较的次数依次减少。例如,对于5个元素的数列,第一轮需要比较4次,第二轮比较3次,依此类推[3]。这种特性使得冒泡排序的时间复杂度为O(n²),其中n是数列的长度[4]。
尽管冒泡排序在效率上不如许多现代的排序算法(如快速排序和归并排序),它仍然有其应用场景和优点。它是一种稳定的排序算法,即相等的元素在排序后会保持原有的相对顺序[5]。这使得冒泡排序在处理具有相同值且需保持原始顺序的数据时非常有用。此外,由于冒泡排序只需要较小的额外空间(用于元素交换时的临时存储),因此它的空间复杂度为O(1),在资源受限的环境中可能仍有优势[3]。
综上所述,冒泡排序作为一种简单但效率较低的排序算法,以其稳定性和低空间复杂度在某些特定场景下仍有应用价值。然而,对于大规模数据处理或性能要求较高的应用,通常建议使用更高效的排序算法。
//冒泡排序
void BubbleSort(List* list) {
int i, j; //i标识每趟排序范围最后一个元素下标,每趟排序元素下标范围是0~i
for (i = list->n - 1; i > 0; i--) {
bool isSwap = false;
for (j = 0; j < i; j++) {
if (list->D[j].key > list->D[j + 1].key) {
swap(list->D[j], list->D[j + 1]);
isSwap = true;
}
}
if (!isSwap) break; //如果本趟排序没有发生元素交换,排序完成
}
}
(四)快速排序
快速排序是一种高效的排序算法,基于分治法的思想,通过递归方式将大问题分解为小问题来解决。
快速排序的基本步骤包括选择基准值(pivot)、分区(partitioning)和递归排序。具体而言,算法首先从待排序的数组中选择一个元素作为基准值,然后将数组中的元素进行分区,使得比基准值小的元素都移动到它的左边,比基准值大的元素都移动到它的右边。接着,算法对基准值左右两个子数组进行递归排序,直到整个数组有序[1][2]。
这种排序方法由于其高效和简洁,在实际应用中广泛被采用。它由英国计算机科学家C.A.R. Hoare在1960年提出,之后因其优秀的平均时间复杂度O(n log n)而受到重视[3]。在最佳情况下,快速排序的时间复杂度可以达到O(n log n),但在最坏情况下(如数组已经是正序或逆序时),它的时间复杂度会降为O(n2)。为了改善最坏情况,可以通过随机选择基准值或者使用“三数取中”法来减少这种情况的发生概率[4][5^]。
//序列划分方法
int Partition(List* list, int low, int high) {
int i = low, j = high + 1;
Entry pivot = list->D[low]; //pivot是分割元素
do {
do i++;
while (list->D[i].key < pivot.key); //i前进
do j--;
while (list->D[j].key > pivot.key); //j前进
if (i < j) swap(list->D[i], list->D[j]);
} while (i < j);
swap(list->D[low], list->D[j]);
return j; //此时j是分割元素下标
}
//快速排序
void QuickSort(List* list, int low, int high) { //快速排序的递归函数
int k;
if (low < high) { //当前待排序序列至少包含2个元素
k = Partition(list, low, high);
QuickSort(list, low, k - 1);
QuickSort(list, k + 1, high);
}
}
//函数重载
void QuickSort(List* list) { //快速排序算法的主调用函数
QuickSort(list, 0, list->n - 1);
}
(五)两路合并排序
//Merge(合并)函数
void Merge(List* list, int Temp[], int i1, int j1, int i2, int j2, int* k) {
int i = i1, j = i2;
while ((i <= j1) && (j <= j2)) { //若两个子序列都不空,则循环
if (list->D[i].key <= list->D[j].key) {
Temp[(*k)++] = list->D[i++].key; //将较小元素存入Temp[*k]
}
else Temp[(*k)++] = list->D[j++].key;
}
while (i <= j1) Temp[(*k)++] = list->D[i++].key; //将子序列1中剩余元素存入Temp
while (j <= j2) Temp[(*k)++] = list->D[j++].key; //将子序列2中剩余元素存入Temp
}
//MergeSort函数,参考了陈慧南老师的《数据结构——C语言描述》教材
void MergeSort(List* list) {
int Temp[MaxSize];
int i1, j1, i2, j2, i, k, size = 1; //i1,j1和i2,j2分别是两个子序列的上,下界
while (size < list->n) {
i1 = 0;
k = 0;
while (i1 + size < list->n) { //若i1+size < n,则说明存在两个子序列,需要再两两合并
i2 = i1 + size; //确定子序列2的下界和子序列1的上界
j1 = i2 - 1;
if (i2 + size - 1 > list->n - 1) { //设置子序列2的上界
j2 = list->n - 1;
}
else j2 = i2 + size - 1;
Merge(list, Temp, i1, j1, i2, j2, &k); //合并相邻两个子序列
i1 = j2 + 1; //确定下一次合并第一个子序列的下界
}
for (i = 0; i < k; i++) {
list->D[i].key = Temp[i];
}
size *= 2; //子序列长度扩大一倍
}
}
(六)堆排序
void AdjustDown(List* list, int r, int j)
{
int child = 2 * r + 1;
Entry temp = list->D[r];
while (child <= j)
{
if ((child < j) && (list->D[child].key < list->D[child+1].key))
child++;
if (temp.key >= list->D[child].key)
break;
list->D[(child - 1) / 2] = list->D[child];
child = 2 * child + 1;
}
list->D[(child - 1) / 2] = temp;
}
void HeapSort(List* list)
{
int n = list->n;
for (int i = (n - 2) / 2; i > -1; i--)
AdjustDown(list, i, n - 1);
for (int i = n - 1; i > 0; i--)
{
swap(list->D[0], list->D[i]);
AdjustDown(list, 0, i - 1);
}
}
二、全部排序整合+时间测试
#include<stdio.h>
#include <stdlib.h>//用到rand()函数
#include<time.h> //用到clock()函数
using namespace std;
#include<algorithm>
#define KeyType int
#define DataType int
#include<iostream>
#include <cassert>
#define N 10000
#define MAX 32768
// 99999
int MyRandom() {
int a0, a = 0;
a0 = rand() % 10000;
a = rand() % 10;
return a * 10000 + a0;
}
typedef struct entry //数据元素
{
KeyType key; //排序关键字,KeyType应该为可比较类型
DataType data; //data包含数据元素中的其他数据项
}Entry;
typedef struct list { //顺序表
int n; //待排序数据元素数量
Entry* D; //静态数组存储数据元素
}List;
typedef struct Peer
{
unsigned int start;
unsigned int end;
}Peer;
typedef struct Stack
{
Peer* data;
int top;
}Stack;
//简单选择排序
void SelectSort(List* list) {
int small, i, j;
// 要排序 n-1 次
for (i = 0; i < list->n - 1; i++) {
small = i; //标记每次第一位元素下标
for (j = i + 1; j < list->n; j++) {
if (list->D[j].key < list->D[small].key) { //按非递减顺序比较大小
small = j;
}
}
if (small != i) {
swap(list->D[i], list->D[small]);
}
}
}
//直接插入排序
void InsertSort(List* list) {
int i, j;
//i标识待插入元素下标
for (i = 1; i < list->n; i++) {
Entry insertItem = list->D[i]; //标记每次第一位元素
for (j = i - 1; j >= 0; j--) {
//不断将有序序列中元素向后移动,为待插入元素空出一个位置
if (insertItem.key < list->D[j].key) {
list->D[j + 1] = list->D[j];
}
else break;
}
list->D[j + 1] = insertItem; //待插入元素有序存放至有序序列中
}
}
//冒泡排序
void BubbleSort(List* list) {
int i, j; //i标识每趟排序范围最后一个元素下标,每趟排序元素下标范围是0~i
for (i = list->n - 1; i > 0; i--) {
bool isSwap = false;
for (j = 0; j < i; j++) {
if (list->D[j].key > list->D[j + 1].key) {
swap(list->D[j], list->D[j + 1]);
isSwap = true;
}
}
if (!isSwap) break; //如果本趟排序没有发生元素交换,排序完成
}
}
//Merge(合并)函数
void Merge(List* list, int Temp[], int i1, int j1, int i2, int j2, int* k) {
int i = i1, j = i2;
while ((i <= j1) && (j <= j2)) { //若两个子序列都不空,则循环
if (list->D[i].key <= list->D[j].key) {
Temp[(*k)++] = list->D[i++].key; //将较小元素存入Temp[*k]
}
else Temp[(*k)++] = list->D[j++].key;
}
while (i <= j1) Temp[(*k)++] = list->D[i++].key; //将子序列1中剩余元素存入Temp
while (j <= j2) Temp[(*k)++] = list->D[j++].key; //将子序列2中剩余元素存入Temp
}
// 两路合并函数
void MergeSort(List* list) {
int Temp[N];
int i1, j1, i2, j2, i, k, size = 1; //i1,j1和i2,j2分别是两个子序列的上,下界
while (size < list->n) {
i1 = 0;
k = 0;
while (i1 + size < list->n) { //若i1+size < n,则说明存在两个子序列,需要再两两合并
i2 = i1 + size; //确定子序列2的下界和子序列1的上界
j1 = i2 - 1;
if (i2 + size - 1 > list->n - 1) { //设置子序列2的上界
j2 = list->n - 1;
}
else j2 = i2 + size - 1;
Merge(list, Temp, i1, j1, i2, j2, &k); //合并相邻两个子序列
i1 = j2 + 1; //确定下一次合并第一个子序列的下界
}
for (i = 0; i < k; i++) {
list->D[i].key = Temp[i];
}
size *= 2; //子序列长度扩大一倍
}
}
void Init(List* list) {
list->n = N;
list->D = (Entry*)malloc(sizeof(Entry) * list->n);
for (int i = 0; i < list->n; i++) {
list->D[i].key = i;
}
for (int j = 0; j < N; j++) {
int l, r;
if (N > MAX) {
l = MyRandom();
r = MyRandom();
}
else {
l = rand() % N;
r = rand() % N;
}
swap(list->D[l].key, list->D[r].key);
}
}
void Print(List* list) {
for (int i = 0; i < list->n; i++) {
cout << list->D[i].key << "\t";
if (i % 20 == 19)
cout << endl;
}
}
void AdjustDown(List* list, int r, int j)
{
int child = 2 * r + 1;
Entry temp = list->D[r];
while (child <= j)
{
if ((child < j) && (list->D[child].key < list->D[child+1].key))
child++;
if (temp.key >= list->D[child].key)
break;
list->D[(child - 1) / 2] = list->D[child];
child = 2 * child + 1;
}
list->D[(child - 1) / 2] = temp;
}
void HeapSort(List* list)
{
int n = list->n;
for (int i = (n - 2) / 2; i > -1; i--)
AdjustDown(list, i, n - 1);
for (int i = n - 1; i > 0; i--)
{
swap(list->D[0], list->D[i]);
AdjustDown(list, 0, i - 1);
}
}
int Partition(List* list, int low, int high) {
int i = low, j = high + 1;
Entry pivot = list->D[low]; //pivot是分割元素
do {
do i++;
while (list->D[i].key < pivot.key); //i前进
do j--;
while (list->D[j].key > pivot.key); //j前进
if (i < j) swap(list->D[i], list->D[j]);
} while (i < j);
swap(list->D[low], list->D[j]);
return j; //此时j是分割元素下标
}
void QuickSortNoR(List* list)
{
int len = N;
int size = (int)(log((double)len) / log((double)2)) + 1;
Stack st;
st.data = (Peer*)malloc(sizeof(Peer) * size);
assert(st.data != NULL);
st.top = 0;
Peer peer;
peer.start = 0;
peer.end = len - 1;
st.data[st.top++] = peer;
while (st.top != 0)
{
peer = st.data[--st.top];
int mod = Partition(list, peer.start, peer.end);
if (peer.end - mod > 1)
{
Peer new_peer = { mod + 1, peer.end };
st.data[st.top++] = new_peer;
}
if (mod - peer.start > 1)
{
Peer new_peer = { peer.start, mod - 1 };
st.data[st.top++] = new_peer;
}
}
}
int main() {
int begintime, endtime;
cout << "原序列为:" << endl;
List* list = new List;
//Init(list);
//Print(list);
//list = new List;
//Init(list);
//begintime = clock();
//cout << endl << "简单选择排序" << endl; // 9200ms
//SelectSort(list);
Print(list);
//endtime = clock();
//printf("\nRunning Time:%dms\n", endtime - begintime);
//cout << endl;
//list = new List;
//Init(list);
//begintime = clock();
//cout << "直接插入排序" << endl; // 5300ms
//InsertSort(list);
Print(list);
//endtime = clock();
//printf("\nRunning Time:%dms\n", endtime - begintime);
//cout << endl;
//list = new List;
//Init(list);
//begintime = clock();
//cout << "冒泡排序" << endl;
//BubbleSort(list);
//endtime = clock();
//printf("\nRunning Time:%dms\n", endtime - begintime);
Print(list);
//cout << endl;
list = new List;
Init(list);
begintime = clock();
cout << "快速排序" << endl;
QuickSortNoR(list);
//Print(list);
endtime = clock();
printf("\nRunning Time:%dms\n", endtime - begintime);
cout << endl;
//list = new List;
//Init(list);
Print(list);
//begintime = clock();
//cout << "两路合并排序" << endl;
//MergeSort(list);
//endtime = clock();
//printf("\nRunning Time:%dms\n", endtime - begintime);
Print(list);
//cout << endl;
//list = new List;
//Init(list);
Print(list);
//begintime = clock();
//cout << "堆排序" << endl;
//HeapSort(list);
//endtime = clock();
//printf("\nRunning Time:%dms\n", endtime - begintime);
Print(list);
//cout << endl;
return 0;
}
/* 简单选择排序、
直接插入排序、
冒泡排序、
快速排序、
两路合并排序
堆排序。 */
}
/* 简单选择排序、
直接插入排序、
冒泡排序、
快速排序、
两路合并排序
堆排序。 */
三、算法时间比较簇行柱状图
各种算法时间柱状图比较