数据结构实验——实现希尔排序的算法、实现快速排序的算法、实现堆排序的算法

实验题1:实现希尔排序的算法

题目描述

  1. 内容:编写一个程序exp10-3.cpp实现希尔排序算法,用相关数据进行测试,并输出各趟的排序结果。
  2. 算法:exp10-3.cpp程序,采用希尔排序方法在包含函数 ShellList(RecType R[],intn,KeyType k),即采用快速排序方法在R[0,n-1]递增有序中查找关键字为k的记录的位置。

运行代码

Seqlist.cpp
#include <stdio.h>
#include <stdlib.h>  
#define MAXL 100
// 定义关键字类型为int
typedef int KeyType;
// 假设其他数据项类型为char,可根据实际需求修改
typedef char InfoType;
// 声明查找顺序表元素类型
typedef struct {
    KeyType key;
    InfoType data;
} RecType;
// 创建顺序表
void CreateList(RecType R[], KeyType keys[], int n) {
    for (int i = 0; i < n; i++) {
        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");
}
exp10-3.cpp
#include "seqList.cpp"
void ShellSort(RecType R[], int n) {
	int i, j, d;
	RecType tmp;
	d = n / 2;
	while (d > 0) {
		for (int i = d; i < n; i++) {
			tmp = R[i];
			j = i - d;
			while (j >= 0 && tmp.key < R[j].key) {
				R[j + d] = R[j];
				j = j - d;
			}
			R[j + d] = tmp;
		}
		printf("  d=%d: ", d);
		DispList(R, n);
		d = d / 2;
	}
}
int main() {
	int n = 10;
	RecType R[MAXL];
	KeyType a[] = { 9,8,7,6,5,4,3,2,1,0 };
	CreateList(R, a, n);
	printf("排序前:");
	DispList(R, n);
	ShellSort(R, n);
	printf("排序后:");
	DispList(R, n);
	return 1;
}

代码思路

  1. 实现了一个简单的顺序表数据结构,并在其上应用了希尔排序(Shell Sort)算法对顺序表中的元素进行排序,最后展示排序前后顺序表中元素的情况。
  2. 数据结构定义部分通过 typedef 定义了 KeyType 为 int 类型,用于表示关键字(通常是用于排序、查找等操作的关键数据部分),InfoType 定义为 char 类型(注释中表明可根据实际需求修改,用于表示其他相关的数据项)。定义了 RecType 结构体,它包含 key(关键字)和 data(其他数据项)两个成员,代表顺序表中的一个元素。
  3. 希尔排序算法实现(ShellSort 函数):首先初始化了间隔 d 为 n / 2,然后进入外层 while 循环,只要 d 大于 0,就持续进行排序过程。在内层 for 循环中,从间隔位置 d 开始遍历顺序表,对于每个位置 i,先将 R[i] 暂存到 tmp 变量中,然后通过内层 while 循环,按照间隔 d 往前比较和移动元素,如果 tmp.key 小于当前比较位置 j 的元素的 key 值,就将 R[j] 后移 d 个位置(即 R[j + d] = R[j]),并更新 j 的值继续往前比较,直到找到合适的插入位置(即 j 不满足条件时),再将暂存的元素 tmp 插入到该位置(R[j + d] = tmp)。在每次间隔调整前,还输出了当前间隔 d 以及对应的顺序表元素情况,方便观察排序过程中的中间状态。
  4. 虽然代码中没有显式地进行动态内存分配(如 malloc、free 等操作),但定义的顺序表数组 R 是基于固定大小 MAXL 的静态数组。如果实际应用中需要处理的数据量可能超过这个固定大小,就需要考虑改为动态内存分配的方式来管理顺序表的存储空间,避免出现数组越界等问题。
  5. 数据完整性问题在 CreateList 函数里只对 key 成员进行了赋值,对于 data 成员未做处理,如果在实际情况中 data 承载了重要信息,需要补充相应的赋值逻辑,保证数据的完整性,否则可能导致后续使用 data 时出现未初始化的错误情况。
  6. 排序算法的稳定性和效率验证:稳定性:希尔排序本身是一种不稳定的排序算法,但在某些特定应用场景可能需要稳定的排序结果,这时就需要考虑是否要更换排序算法或者对希尔排序进行适当改进(不过通常改进希尔排序实现稳定较为复杂)。效率验证:可以通过改变输入数据的规模(如增大 n 的值)、不同的数据分布情况(有序、逆序、随机等)来测试希尔排序算法的时间复杂度表现,对比理论上的时间复杂度情况,观察代码实际的性能是否符合预期,以便在必要时对算法进行优化。
  7. 代码的可维护性和扩展性:当前代码功能相对简单,如果后续要添加更多对顺序表的操作(如插入、删除特定元素等)或者支持不同类型的 KeyType 和 InfoType(比如改为 float 等其他类型),代码的结构可能需要进一步调整优化,比如将一些操作封装成更独立、通用的函数,遵循良好的代码设计原则来提高可维护性和扩展性。

实验题2:实现快速排序的算法

题目描述

内容:编写一个程序 exp10-5.cpp,实现快速排序算法,用相关数据进行测试,并输出各次划分的排序结果。

2.算法:exp10-5.cpp程序,其中包含如下函数:

·disppart(RecType R[],ints,int t):显示 R[s..t]划分后的结果。

partition(RecType R[],ints,int t):对 R[s..]元素进行一趟划分(以该区间中第一个元素为基准)。

·QuickSort(RecType R[],int s,intt):对 R[s..]的元素进行递增快速排序。

运行代码

#include "seqlist.cpp"
// 划分函数,用于快速排序中对区间进行划分
int partition(RecType R[], int s, int t) {
    int i = s, j = t;
    RecType tmp = R[s];  // 这里以区间第一个元素作为基准值,修改为R[s]更合理
    while (i < j) {
        // 从右往左找第一个小于基准值的元素
        while (j > i && R[j].key >= tmp.key) {
            j--;
        }
        if (j > i) {
            R[i] = R[j];
            i++;
        }
        // 从左往右找第一个大于基准值的元素
        while (i < j && R[i].key <= tmp.key) {
            i++;
        }
        if (i < j) {
            R[j] = R[i];
            j--;
        }
    }
    R[i] = tmp;
    return i;
}
// 用于展示每次划分后的区间情况
void Disppart(RecType R[], int s, int t) {
    static int i = 1;
    printf("第%d次划分:", i);
    for (int j = 0; j < s; j++) {
        printf("    ");  // 适当增加空格,让输出更美观
    }
    for (int j = s; j <= t; j++) {
        printf(" %3d", R[j].key);
    }
    printf("\n");
    i++;
}
// 快速排序函数主体
void QuickSort(RecType R[], int s, int t) {
    if (s < t) {
        int i = partition(R, s, t);
        Disppart(R, s, t);  // 展示本次划分后的区间情况
        QuickSort(R, s, i - 1);
        QuickSort(R, i + 1, t);
    }
}
int main() {
    int n = 10;
    RecType R[MAXL];
    KeyType a[] = { 6, 7, 8, 9, 0, 1, 2, 3, 4, 5 };
    CreateList(R, a, n);
    printf("排序前:");
    DispList(R, n);
    QuickSort(R, 0, n - 1);  // 快速排序时传入正确的起始和结束下标
    printf("排序后:");
    DispList(R, n);
return 1;
}

代码思路

  1. partition函数:这是快速排序的核心函数之一,用于对给定区间[s, t]进行划分。它选择区间的第一个元素作为基准值,通过双指针法将区间内的元素重新排列,使得左边的元素都小于等于基准值,右边的元素都大于等于基准值,并返回基准值最终的位置。初始化i = s和j = t,并将基准值tmp设为R[s]。通过两个嵌套的while循环实现双指针移动。外层while循环保证i < j,内层第一个while循环从右往左找第一个小于基准值的元素,找到后如果j > i,则将R[j]赋值给R[i]并i++。内层第二个while循环从左往右找第一个大于基准值的元素,找到后如果i < j,则将R[i]赋值给R[j]并j--。最后将基准值tmp放到正确的位置R[i],并返回i 。
  2. Disppart函数:用于展示每次划分后的区间情况,通过缩进和格式化输出,使每次划分的结果直观地显示出来。使用静态变量i记录划分的次数,每次调用函数时输出 “第i次划分:”。通过循环输出空格来实现缩进,然后输出区间[s, t]内元素的值,并换行。每次调用后i自增。
  3. QuickSort函数:实现快速排序算法的主体逻辑。它递归地对划分后的子区间进行排序,直到区间长度小于等于 1。首先判断如果s < t,则调用partition函数对区间[s, t]进行划分,得到基准值的位置i。然后调用Disppart函数展示本次划分后的区间情况。接着递归地对左子区间[s, i - 1]和右子区间[i + 1, t]调用QuickSort函数进行排序。
  4. 排序算法性能问题:快速排序在最坏情况下(例如数据已经有序或逆序)的时间复杂度会退化为 O (n²)。在实验中,可以通过不同的数据分布(有序、逆序、随机等)来测试算法性能,观察是否存在性能退化的情况。如果需要,可以考虑采用随机化选择基准值等优化策略来改善最坏情况性能。
  5. 静态变量的影响:Disppart函数中的静态变量i用于记录划分次数。在多次调用QuickSort函数进行不同数据排序时,i的值会持续增加。如果希望每次对新数据排序时i都从 1 开始,需要对i进行重置处理,或者考虑将i作为参数传递给Disppart函数,使其不依赖于静态变量。

实验题3:实现堆排序的算法

题目描述

1.内容:编写程序exp10-7.cpp,采用堆排序方法,用相关数据进行测试,并输出各趟的排序结果。

2.算法:exp10-7.cpp程序包含下列函数:。

· DispHeap(RecType R[],int i,int n):以括号表示法输出建立的堆R[1 .. n]

·Sift(RecType R[],int low,int high):对R[low .. high」进行堆筛选的算法。

· HeapSort(RecType R[],int n):对R[1 .. n]元素序列实现堆排序。

运行代码

格式有点问题
#include "seqlist.cpp"
int count = 1;
// 以括号表示法输出建立的堆
void DispHeap(RecType R[], int i, int n) {
    if (i <= n) {
        printf("%d", R[i].key);
        if (2 * i <= n || 2 * i + 1 < n) {
            printf("(");
            if (2 * i <= n) {
                DispHeap(R, 2 * i, n);
            }
            printf(",");
            if (2 * i + 1 <= n) {
                DispHeap(R, 2 * i + 1, n);
            }
            printf(")");
        }
    }
}
// R[low..high]堆筛选算法
void Sift(RecType R[], int low, int high) {
    int i = low, j = 2 * i;
    RecType temp = R[i];
    while (j <= high) {
        // 若右孩子较大,则把j指向右孩子
        if (j < high && R[j].key < R[j + 1].key) {
            j++;
        }
        if (temp.key < R[j].key) {
            R[i] = R[j];
            i = j;
            j = 2 * i;
        }
        else {
            break;
        }
    }
    R[i] = temp;
}
// 堆排序函数
void HeapSort(RecType R[], int n) {
    int i,j;
    // 循环建立初始堆
    for (i = n / 2; i >= 1; i--) {
        Sift(R, i, n);
    }
    printf("初始堆 ");
    DispHeap(R, 1, n);
    printf("\n");
    // 进行n - 1次循环,完成堆排序
    for (i = n; i >= 2; i--) {
        printf("第%d趟排序:", count++);
        printf("交换%d与%d,输出%d", R[i].key, R[1].key, R[1].key);
        // 将第一个元素和当前区间内的R[1]交换
        RecType tmp = R[1];
        R[1] = R[i];
        R[i] = tmp;
        printf("排序结果:");
        for (int j = 1; j <= n; j++) {
            printf("%2d", R[j].key);  // 修正此处,将R[i].key改为R[j].key,正确输出排序结果
        }
        // 筛选R[1]结点,得到i - 1个结点的堆
        Sift(R, 1, i - 1);
        printf("筛选调整得到堆:");
        DispHeap(R, 1, i - 1);
        printf("\n");
    }
}
int main() {
    int n = 10;
    RecType R[MAXL];
    KeyType a[] = { 6, 8, 7, 9, 0, 1, 3, 2, 4, 5 };
    CreateList(R, a, n);
    printf("排序前: ");
    DispList(R, n);
    HeapSort(R, n);
    printf("排序后: ");
    DispList(R, n);
    return 1;
}
正确代码
//顺序表基本运算算法
#include <stdio.h>
#define MAXL 100        //最大长度
typedef int KeyType;    //定义关键字类型为int
typedef char InfoType;

typedef struct
{
    KeyType key;        //关键字项
    InfoType data;        //其他数据项,类型为InfoType
} RecType;                //查找元素的类型

void swap(RecType& x, RecType& y)    //x和y交换
{
    RecType tmp = x;
    x = y; y = tmp;
}

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 CreateList1(RecType R[], KeyType keys[], int n)    //创建顺序表
{
    for (int i = 1; i <= n; i++)            //R[1..n]存放排序记录
        R[i].key = keys[i - 1];
}
void DispList1(RecType R[], int n)    //输出顺序表
{
    for (int i = 1; i <= n; i++)
        printf("%d ", R[i].key);
    printf("\n");
}
#include "seqlist.cpp"                    //包含排序顺序表的基本运算算法

int count = 1;                            //全局变量
void DispHeap(RecType R[], int i, int n)    //以括号表示法输出建立的堆
{
    if (i <= n)
        printf("%d", R[i].key);            //输出根节点
    if (2 * i <= n || 2 * i + 1 < n)
    {
        printf("(");
        if (2 * i <= n)
            DispHeap(R, 2 * i, n);            //递归调用输出左子树
        printf(",");
        if (2 * i + 1 <= n)
            DispHeap(R, 2 * i + 1, n);        //递归调用输出右子树
        printf(")");
    }
}
void Sift(RecType R[], int low, int high)        //R[loww..high}堆筛选算法
{
    int i = low, j = 2 * i;                         //R[j]是R[i]的左孩子
    RecType temp = R[i];
    while (j <= high)
    {
        if (j < high && R[j].key < R[j + 1].key)     //若右孩子较大,把j指向右孩子
            j++;                            //变为2i+1
        if (temp.key < R[j].key)
        {
            R[i] = R[j];                      //将R[j]调整到双亲节点位置上
            i = j;                            //修改i和j值,以便继续向下筛选
            j = 2 * i;
        }
        else break;                         //筛选结束
    }
    R[i] = temp;                              //被筛选节点的值放入最终位置
}
void HeapSort(RecType R[], int n)    //对R[1]到R[n]元素实现堆排序
{
    int i, j;
    for (i = n / 2; i >= 1; i--)          //循环建立初始堆
        Sift(R, i, n);
    printf("初始堆:"); DispHeap(R, 1, n); printf("\n");    //输出初始堆
    for (i = n; i >= 2; i--)       //进行n-1次循环,完成推排序
    {
        printf("第%d趟排序:", count++);
        printf(" 交换%d与%d,输出%d ", R[i].key, R[1].key, R[1].key);
        swap(R[1], R[i]);        //将第一个元素同当前区间内R[1]对换
        printf(" 排序结果:");    //输出每一趟的排序结果
        for (j = 1; j <= n; j++)
            printf("%2d", R[j].key);
        printf("\n");
        Sift(R, 1, i - 1);             //筛选R[1]节点,得到i-1个节点的堆
        printf("筛选调整得到堆:"); DispHeap(R, 1, i - 1); printf("\n");
    }
}
int main()
{
    int n = 10;
    RecType R[MAXL];
    KeyType a[] = { 6,8,7,9,0,1,3,2,4,5 };
    CreateList1(R, a, n);
    printf("排序前:"); DispList1(R, n);
    HeapSort(R, n);
    printf("排序后:"); DispList1(R, n);
    return 1;
}


12-05 10:34:54
//顺序表基本运算算法
#include <stdio.h>
#define MAXL 100        //最大长度
typedef int KeyType;    //定义关键字类型为int
typedef char InfoType;

typedef struct
{
    KeyType key;        //关键字项
    InfoType data;        //其他数据项,类型为InfoType
} RecType;                //查找元素的类型

void swap(RecType& x, RecType& y)    //x和y交换
{
    RecType tmp = x;
    x = y; y = tmp;
}

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 CreateList1(RecType R[], KeyType keys[], int n)    //创建顺序表
{
    for (int i = 1; i <= n; i++)            //R[1..n]存放排序记录
        R[i].key = keys[i - 1];
}
void DispList1(RecType R[], int n)    //输出顺序表
{
    for (int i = 1; i <= n; i++)
        printf("%d ", R[i].key);
    printf("\n");
}

代码思路

不知道为什么这个代码输出的结果和预期中的结果,发现有一个数据的读取报错然后不对,然后对代码进行进一步改进如下:

  1. DispHeap函数:以括号表示法递归地输出建立的堆结构。如果当前节点i小于等于堆的大小n,则先输出当前节点的key值,然后判断是否有左右子节点,如果有则输出左括号,递归调用DispHeap输出左子树,输出逗号,递归调用DispHeap输出右子树,最后输出右括号。
  2. Sift函数:实现堆筛选算法,用于维护堆的性质(大顶堆)。首先记录当前节点i及其左子节点j(j = 2 * i),并将当前节点的值暂存到temp中。然后进入循环,只要j小于等于堆的上界high,就进行以下操作:如果j小于high且右子节点的key值大于左子节点的key值,则将j指向右子节点。如果temp的key值小于R[j]的key值,则将R[i]赋值为R[j],更新i和j的值(i = j,j = 2 * i),继续循环。否则,跳出循环。最后将暂存的值temp赋值给R[i],完成筛选操作。
  3. 堆排序函数HeapSort:实现堆排序算法。首先通过循环(for (i = n / 2; i >= 1; i--))调用Sift函数来建立初始堆。然后输出初始堆的结构(通过DispHeap函数)。接着进行n - 1次循环,每次循环:交换R[1]和R[i](RecType tmp = R[1]; R[1] = R[i]; R[i] = tmp;),将当前最大元素放到数组末输出当前趟数的排序信息(printf("第%d趟排序:", n - i + 1);)和排序结果(通过DispList函数的逻辑)。调用Sift函数对新的R[1]进行堆调整,保持R[1]到R[i - 1]是一个堆,并输出调整后的堆结构(通过DispHeap函数)。
  4. 数组下标问题:代码中的数组下标从 1 开始(在HeapSort、Sift和DispHeap等函数中都有体现),与 C 语言中数组下标通常从 0 开始的习惯不同。这种下标方式可能会导致理解和调试代码时容易出现混淆,需要特别注意。
  5. 数据完整性问题:在CreateList函数中,只对R数组元素的key成员进行了赋值,而data成员未被处理。如果在实际应用中data成员有意义,需要补充相应的赋值逻辑,以保证数据的完整性。
  6. 内存管理问题:虽然代码中没有显式地进行动态内存分配,但定义的顺序表R是基于固定大小MAXL的静态数组。如果处理的数据量可能超过这个固定大小,需要考虑改为动态内存分配的方式来管理顺序表的存储空间,避免数组越界等问题。
  7. 排序算法性能验证问题:可以通过改变输入数据的规模(增大n的值)、不同的数据分布情况(有序、逆序、随机等)来测试堆排序算法的时间复杂度表现,对比理论上的时间复杂度情况,观察代码实际的性能是否符合预期,以便在必要时对算法进行优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

筱姌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值