一.多个排序法的综合实验日志
实验要求: 综合排序问题【问题描述】分别采用表结构、树结构和散列结构(可选)对一定规模的数据集进行排序。
[任务要求:
-
至少采用四种排序方法实现随机生成1000个数值的排序(可采用的排序方法有插入排序、希尔排序、冒泡排序、快速排序、选择排序、堆排序、归并排序)。并把排序后的结果保存在不同的文件中。
-
统计每一种排序方法的性能(以上机运行程序所花费的时间为准进行对比),找出其中两种较快的方法。
3.问题分析和任务定义:
①需求分析:在该部分中叙述本课程设计要完成的任务、提供的功能,系统每个模块的功能要求;
任务: 实现
二.实现
2.1 表结构
表结构的话主要采用的是顺序表进行操作,然后首先进行的便是对于随机数值排序,设计思路如下
- 1000个随机数生成
- 1000个随机数放入表结构
- 排序
随机数生成模块部分需要写在主函数中才能起作用
//0.随机数的生成
srand((unsigned)time(NULL));
for (int i = 0; i < 1000; i++)
{
InitNum[i] = rand() %1000;
printf("%d ",InitNum[i]);
}
2.2 冒泡排序
基本原理:设置两个移动指针,挨两个比较并进行后移,若第二个数比第一个数小则进行交换,并进行循环范围。因此需要进行双重循环,其时间复杂度为O(n^2),因其空间占用就交换部分,因此空间复杂度为O(1)
实现步骤:
1.设置双层循环,内层循环的结束调节随着第一层循环改变而改变
2.在内层循环中进行相邻间数字的比较并交换
for (int i = 0; i < 1000; i++)
{
for (int j = 0; j+1 < 1000-i; j++)
{
if (InitNum[j] > InitNum[j+1])
{
Swap(&InitNum[j], &InitNum[j + 1]);
}
}
}
void Swap(int *a,int *b) { int temp = *a; *a = *b; *b = temp; }
交换算法
总结: 数组作为函数参数进行传递时,传递的是数组的首地址指针。
2.3 选择排序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BXfyKv3R-1631262615314)(G:\数据结构\综合实验2.0\images\3.选择排序.gif)]
原理概述:设置两个指针,双层循环,其中第一个先固定,第二个指针不断将每次的数据与第一个相比较,再决定交换与否,每次完成一个轮回后,需要调整第二层开始的位置。
实现步骤:
1.设置双层循环,内层循环的开始调节随着第一层循环改变而改变
2.在内层循环中进行相间数字的比较并交换
void Choice(int Num[1000])
{
for (int i = 0; i < 1000; i++)
{
for (int j = i + 1; j < 1000; j++)
{
if (Num[i] > Num[j])
{
Swap(&Num[i], &Num[j]);
}
}
}
printf("选择排序的时间效率:O(n^2)\n");
}
**总结:**选择排序因为主要也是涉及到一个双层循环的一个应用,且每次循环每个都会进行比较,因此其时间复杂度也为O(n^2)。
2.4 插入排序
原理分析:类似于斗地主打扑克的原理,当抓到已有的牌之后进行一个排序,再抓到新的牌时再在已有的基础上再将新牌进行插入,最坏情况下的插入是每个元素都进行n-1次完整循环(n(n-1))所以其时间复杂度为O(n^2)*。
实现步骤:
1.定义初始位置,一般是从第二个位置开始,中间变量存储初始位置元素
2.寻找插入位置,通常是从初始位置倒向开始,满足条件即可停止寻找,直到最初位置
3.元素移动:插入位置的后一个元素到初始位置前一个元素一一挨个后移
4.插入元素:插入位置后一个元素插入中间变量元素
for (int i = 1; i < 1000; i++)
{
int temp = InitNum[i];
int j = i - 1;
for (j; InitNum[j] > InitNum[i] && j >= -1;j--);//寻找插入位置,大于-1是为了保证最初第一个元素的成功插入。
for (int m = i-1; m >= j && m >= 0; m--) //倒向移动位置
{
InitNum[m+1] = InitNum[m];//元素后移
}
InitNum[j+1] = temp;
}
总结: 插入排序需要注意的就是被插元素的一个中途保存,还有就是插入位置的一个寻找。
2.5快速排序
*概述:*快速排序整体而言就是属于一种分而治之的一种思想的一种体现。快速排序通常要使用递归去实现的
使用快速排序时,每个元素的位置可能存在多次移动,因此快速排序并不能算是一种稳定的算法,快速排序也算是冒泡排序的一种升级版本。
快速排序并不适用于重复数据的排序
实现步骤:
1.找取一个元素为基本参照元素a[0],将整个顺序表区域分为左右两部分
2.在a[0]左半部分中a[L] > a[0]的元素a[R],在a[0]右半部分中寻找a[R] < a[0]的一个元素,当两部分的“寻找人”(指针)重合即代表没有找到,停止寻找。
两个“寻找人”指针分别为low和high,low的初始值为表的初始位置
high的初始值为表的最末位置;
low不断++进行右移,high不断–进行左移,发生交换或是移动到low==high的地步停止移动
(单项移动时最多到参照值的位置)
3.交换a[L]与a[R]的位置,更换a[0]的位置,直到所有元素都进行了处理
1.假设最开始的数据为 6(基数) 1 2 7 9 3 4 5 10 8
2. 小j先往左边走,到达目标位置后,小i再往右边走,到达目标位置后进行交换
4.不断重复步骤2直到小j与小i的相遇
5.相遇之后再将基数位置与相遇位置的数进行交换
之所以之前是小j先走,因为如果小i先走的话最后一次相遇位置会变为9元素的那个位置,则会再将9与基数位置去交换 这显然是不对的.
6.再以为ij相遇的点为分界线,将其分为作半部分和右半部分,再分别对左半部分和右半部分进行重复这六部操作.
代码:
void QuickInsert(int Num[10],int left,int right)
{
int low, high;
int key = Num[left]; //选取左边为基数
high = right,low = left;
if (left >= right)
{
return;
}
while (high != low)
{
//两个循环顺序不可颠倒
for (; Num[high] >= key && low < high; high--);//因为key的左边也能移动,因此可以等于
//printf("\n%d\n", high);
for (; Num[low] <= key && low < high; low++);
if (low < high)
{
Swap(&Num[low], &Num[high]);
}
} //两个for循环顺序不可颠倒
Swap(&Num[low], &Num[left]);
printf("\n%d\n",low);
for (int i = 0; i < 10; i++)
{
printf("%d ", Num[i]);
}
printf("\n");
//每次需要将两边同时进行处理
QuickInsert(Num,low+1,right); //注意递归操作时边界线的设置
QuickInsert(Num, left, low-1);
}
总结: 需要注意的一点就是最后的递归操作时边界线设置,然后就是递归是每一次的左右两半部分都要进行递归操作,才能达到排序的真正目的。
3.树结构排序
概述: 树结构排序的话主要采用的便是二叉排序树。
3.1二叉排序树简介
(1) 若它的左子树不空,则 左子树 上所有结点的值 均小于 它的根结点的值;
(2) 若它的右子树不空,则 右子树 上所有结点的值 均大于 它的根结点的值;
(3) 它的 左、右子树又分别为二叉排序树 。
其具体创建步骤类似如下:
0.初始时的无序序列如图:
1.选取第一个元素作为根节点:
2.第二个元素比上个节点小(根节点),所以放在左边
3.第三个元素比根元素大,所以放在其右边(先从根元素比较),插入10,首先与根结点比较,发现比 8 大,则要将 10 插入根结点的右子树;根结点 8 的右子树为空,则将 10 作为 8 的右孩子
4.插入 1,首先与根结点比较,比根结点小,则应插入根结点的左子树。再与根结点的左孩子 3 比较,发现比 3 还小,则应插入 3 的左孩子
5.插入 6,先与根结点8比较,小于 8,向左走;再与 3 比较,大于 3,向有走,没有结点,则将6 作为3的右孩子。
6.插入14,先与8比较,比 8 大,向右走;再与8的右孩子10比较,比10大,向右走,没有结点,则将14作为10的右孩子。
依此类推,直到顺序表中的所有元素都插入到树中
由此便是整个二叉排序树的构造过程
int SearchBST(BiTree T, int key, BiTree f, BiTree *p){ //T是当前节点,f表示当前节点的上一节点
if (!T) { // 查找不成功,即到空结点的上一结点便停止
*p = f;
return FALSE;
}else if (key == T->data){
*p = T;
return TRUE;
}else if (key < T->data){ // 在左子树中继续查找
return SearchBST(T->lchild, key, T, p);
}else{ // 在右子树中继续查找
return SearchBST(T->rchild, key, T, p);
}
}
int InsertBST(BiTree * T, int key){
BiTree p,s;
if (!SearchBST( *T, key, NULL, &p)) { // 没找到key,通过寻找来锁定要插入的父节点的位置P
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if (!p)
*T = s; // 插入 s 为新的根结点
else if (key < p->data)
p->lchild = s; //插入 s 为左孩子
else
p->rchild = s; // 插入 s 为右孩子
return TRUE;
}else
return FALSE;
}
插入之前是先进行寻找,若已存在被插元素则不进行插入,若不存在,则会找到被插元素的父元素。再进行判断往哪边插入。
二叉树的输出___中序遍历
void InOrderTraverse(BiTree T){
if (!T)
return;
InOrderTraverse(T->lchild);
printf("%d ", T->data);
InOrderTraverse(T->rchild);
}
带有重复元素方式的快排: http://www.spirytusz.com/quicksort/
最后推荐《坐在马桶上学算法》和《五分钟学算法》这两本书,书籍生动有趣。因作者水平有限,如有错误,不吝赐教。