表的综合排序算法+二叉排序树(附原理分析)

本文详细介绍了在表结构中实现多种排序算法的过程,包括冒泡排序、选择排序、插入排序和快速排序。通过随机生成1000个数值进行排序,并探讨了每种算法的原理和时间复杂度。此外,提到了二叉排序树作为树结构的排序方法。文章强调了排序算法的性能比较,并推荐了相关算法学习资源。
摘要由CSDN通过智能技术生成

一.多个排序法的综合实验日志

实验要求: 综合排序问题【问题描述】分别采用表结构、树结构和散列结构(可选)对一定规模的数据集进行排序。

[任务要求:

  • 至少采用四种排序方法实现随机生成1000个数值的排序(可采用的排序方法有插入排序、希尔排序、冒泡排序、快速排序、选择排序、堆排序、归并排序)。并把排序后的结果保存在不同的文件中。

  • 统计每一种排序方法的性能(以上机运行程序所花费的时间为准进行对比),找出其中两种较快的方法。


3.问题分析和任务定义:

①需求分析:在该部分中叙述本课程设计要完成的任务、提供的功能,系统每个模块的功能要求;

任务: 实现

二.实现

2.1 表结构

表结构的话主要采用的是顺序表进行操作,然后首先进行的便是对于随机数值排序,设计思路如下

  1. 1000个随机数生成
  2. 1000个随机数放入表结构
  3. 排序

随机数生成模块部分需要写在主函数中才能起作用

//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)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oT5dHewv-1631262615312)(G:\数据结构\综合实验2.0\images\2.冒泡规律.gif)]

实现步骤:

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/
最后推荐《坐在马桶上学算法》和《五分钟学算法》这两本书,书籍生动有趣。因作者水平有限,如有错误,不吝赐教。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值