bsjy-by

数据结构与算法设计-头歌-查找与排序算法实验

第1关:二叉排序树的定义及查找

任务描述

本关任务:设计算法在二叉排序树中查找指定值的结点及双亲结点。

相关知识

动态查找表的表结构在查找过程中动态生成,对于给定值key,在表中查找,若存在,则成功返回;否则插入关键字等于key 的记录。
二叉排序树是一种动态查找表。
动态查找表:对查找表除进行查找操作外,还能进行插入、删除和修改操作的表。

二叉排序树定义

二叉排序树又称二叉查找树,它或者是一棵空树,或者是一棵具有如下特性的非空二叉树:
若它的左子树非空,则左子树上所有结点的关键字均小于根结点的关键字;
若它的右子树非空,则右子树上所有结点的关键字均大于根结点的关键字;
左、右子树本身又各是一棵二叉排序树。
一棵二叉排序树示例:

二叉排序树的性质:

1.中序序列是一个递增有序序列。 上述二叉树的中序序列:1,2,3,4,5,6,7,8
2.根结点最左下结点,即为关键字最小的结点
3.根结点最右下结点,即为关键字最大的结点
例1:下列图形中,哪个不是二叉排序树 ?

答案:第二个(提示:第二个左子树的‘6’比根节点‘5’大,二叉排序树中应是左子树的所有点小于根节点,右子树的所有点大于根节点)

二叉排序树查找结点的算法思想

在二叉排序树bt中查找关键字为k的结点,找到后返回该结点的指针,找不到返回NULL。
若当前结点p的关键字等于k,则返回p。
否则若k小于当前结点的关键字,则在左子树中查找。
否则若k大于当前结点的关键字,则在右子树中查找。

查找成功:自树根沿树枝到达目标结点
查找失败:自树根沿树枝到达一个外部结点
成功或失败的最大比较次数都不超过树高。

二叉排序树查找结点的算法性能分析

查找成功时: ASL=(1×1+2×2+3×4+4×3+5×1) /11 = 34/11
查找失败时: ASL=(3×5+4×5+5×2) /12 = 45/12

值得注意的是,ASL值与树的形态有关。
例2:关键字序列为45,24,27,53,12 ,85

ASL=(1×1+2×2+3×3) / 6 = 14/6
例3:关键字序列为12,24,27,45, 53,85

ASL=(1+2+3+4+5+6) / 6 = 21/6

二叉排序树类型定义及查找算法的实现

二叉排序树的结构可以和前面二叉树部分完全类似,结点中的元素类型是结构体类型。

二叉排序树的二叉链存储结构的结点的类型声明如下:

点击查看代码
typedef struct
{
  KeyType key;
}ElemType;
typedef struct BSTNode
{
   ElemType data;
   struct BSTNode *lchild,*rchild
}BSTNode,*BSTree;
点击查看代码
BSTree BSTSearch(BSTree T,KeyType key) //二叉排序树的查找结点的递归算法
{
   if((!T) || key==T->data.key) 
           return T;    
   else if (key<T->data.key)  
         return BSTSearch(T->lchild,key);          
           else 
        return BSTSearch(T->rchild,key);    
} // 
BSTNode *BSTSearch(BSTNode *bt,KeyType k)   //二叉排序树的查找结点的非递归算法
{  
    BSTNode *p=bt;
   while (p!=NULL)
   {    
           if (p->data.key==k)        //找到关键字为k的结点
          return p;
        else if (k<p->data.key)
              p=p->lchild;    //沿左子树查找
            else
             p=p->rchild;        //沿右子树查找
   }
   return NULL;        //未找到时返回NULL
}

编程要求

请在右侧编辑器的命名空间内填写相关代码,设计实现在二叉排序树中查找指定值的结点及双亲结点算法。
查找结点BSTSearch(BiTree bt, KeyType K, BiTree &p, BiTree &f),在二叉排序树bt中查找关键字为k的结点,查找成功,用指针p指向目标,f指向目标的双亲,f初值为NULL。

测试说明

平台会对你编写的代码进行测试:
测试输入:
第一行 1 个整数 n 表示序列的大小
第二行 n 个整数表示序列里的元素
第三行 1 个整数 x
实际输出:
输出你返回的布尔值及双亲结点的值

开始你的任务吧,祝你成功!

点击查看代码
#include <stdio.h>
#include <malloc.h>
#define N 100
typedef int KeyType; // 设关键字域为整型
typedef int InfoType;
typedef struct
{
  KeyType key;
  InfoType otherinfo;
}ElemType;
typedef struct BSTNode
{
   ElemType data;
   struct BSTNode *lchild,*rchild;
}BSTNode,*BSTree;

int InsertBST(BSTNode *&bt,KeyType k);
void CreateBST(BSTNode *&bt,KeyType a[],int n);
void DestroyBST(BSTNode *&bt);
int BSTSearch(BSTree bt, KeyType K, BSTree &p, BSTree &f);  //在根为bt的二叉排序树上查找键值等于K的记录,若查找成功,用指针p指向目标;f指向目标的双亲,f初值为NULL

int main()
{
    BSTree dt=NULL,p=NULL,f=NULL;
    int i,n;
    KeyType j;
    KeyType r[N]={0};   
    scanf("%d",&n);
    for(i=0;i<n;i++)
        scanf("%d",&r[i] ); // 依次插入数据元素
    CreateBST(dt,r,n);
   //printf("\n请输入待查找的值: ");
   scanf("%d",&j);
   BSTSearch(dt,j,p,f);
   if(p)
   {
     printf("表中存在此值。\n");
     if(f)
     printf("其双亲结点为:%d\n",f->data.key);
     else
     printf("此结点为根结点。\n");     
   }
   else
     printf("表中不存在此值。\n");
    DestroyBST(dt);
}
     
void visit(ElemType i)
{
    printf("%d\n",i.key );
}

int BSTSearch(BSTree bt, KeyType K, BSTree &p, BSTree &f)
{
  //在根为bt的二叉排序树上查找键值等于K的记录
  //查找成功,用指针p指向目标;f指向目标的双亲,f初值为NULL
	/********** Begin **********/  
   p = bt;
    while(p){
        if(K<p->data.key){
            f=p;
            p=p->lchild;
        }
        else if(K>p->data.key){
            f=p;
            p=p->rchild;
        }
        else
            return 1;
    }
    return 0;
    /********** End **********/
}

int InsertBST(BSTNode *&bt,KeyType k)
{    
    
   BSTNode *f,*p=bt;
    while (p!=NULL)                    //找插入位置,即找插入新结点的双亲f结点
    {
        if (p->data.key==k)                //不能插入相同的关键字
            return 0;
        f=p;                        //f指向p结点的双亲结点
        if (k<p->data.key)
            p=p->lchild;            //在左子树中查找
        else
            p=p->rchild;            //在右子树中查找
    }
    p=(BSTNode *)malloc(sizeof(BSTNode));
    p->data.key=k;                        //建立一个存放关键字k的新结点
    p->lchild=p->rchild=NULL;        //新结点总是作为叶子结点插入的
    if (bt==NULL)                    //原树为空时,p作为根结点插入
        bt=p;
    else if (k<f->data.key)
        f->lchild=p;                //插入p作为f的左孩子
    else
        f->rchild=p;                //插入p作为f的右孩子
    return 1;                        //插入成功返回1
    
 }

void CreateBST(BSTNode *&bt,KeyType a[],int n)
{
    bt=NULL;                       //初始时bt为空树
    int i=0;
    while (i<n) 
    {      
        InsertBST(bt,a[i]); //将关键字a[i]插入到二叉排序树bt中
        i++;
    }
}
void DestroyBST(BSTNode *&bt)
{
   if (bt!=NULL)
    {
        DestroyBST(bt->lchild);    //销毁左子树
        DestroyBST(bt->rchild);    //销毁右子树
        free(bt);                //释放根结点
    }
}

第2关:采用除留余数法加链地址法建立哈希表

任务描述

本关讨论散列存储,哈希函数使用除留余数法,冲突解决方法采用独立链表地址法。

相关知识

为了完成本关任务,你需要掌握:
1.哈希表结构;
2.根据链地址法处理冲突,设计哈希表。

哈希表的基本概念

哈希表不可避免冲突现象:对不同的关键字可能得到同一哈希地址 即key1≠key2,而hash(key1)=hash(key2)。
具有相同函数值的关键字对该哈希函数来说称为同义词。
因此,在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。可如下描述哈希表:根据设定的哈希函数H(key)和所选中的处理冲突的方法,将一组关键字映象到一个有限的、地址连续的地址集(区间)上并以关键字在地址集中的“象”作为相应记录在表中的存储位置,这种表被称为哈希表。

构造哈希函数的方法

构造哈希函数的目标:使得到的哈希地址尽可能均匀地分布在m个连续内存单元地址上,同时使计算过程尽可能简单以达到尽可能高的时间效率。
根据关键字的结构和分布的不同,有多种构造哈希函数的方法,例如除留余数法。

除留余数法

思想:以关键字被某个数p除后所得余数作为散列地址
公式:H(key) = key % p
关键:p 如何选取?
一般选择小于表长的最大质数
例1:给定存储单元地址为1,000,000至1,000,027,即表长度为28,如何计算关键字172,148的散列地址?
H(key)= 172148 % 23 + 1,000,000
= 16+1,000,000
= 1,000,016

哈希冲突解决方法

本关讨论采用独立链地址法解决冲突问题。
将所有关键字为"同义词"的记录链接在一个线性链表中。此时的散列表以"指针数组"的形式出现,数组内各个分量存储相应散列地址的链表的头指针。
举例:已知一组关键字(19,56,23,14,68,82,70,36,91 ),
散列函数为:hash(key)=key%7,用链地址法处理冲突,构造的哈希表存储结构如下所示:

由 7 个独立链表组成,散列值相同的关键码在同一个链表里,独立链表的头结点组成散列表,一共 7 行,编号 0 , 1 , … , 6 。
哈希表类型定义如下:

点击查看代码
typedef int KeyType; // 设关键字域为整型
struct ElemType // 数据元素类型
{
   KeyType key;     
};
typedef struct Node                 //链地址,其实这个指针就是链表
{
    ElemType r;
    struct Node* next;
}Node;
typedef struct 
{
    Node** rcd;                                //(指向指针的指针)存放指针的数组
    int size;                                //哈希表的容量
    int count;                                //当前表中含有的记录个数    
}HashTable;
定义如下操作,各操作函数的功能详见下面:
点击查看代码
unsigned Hash(KeyType K,int m)
{ // 一个简单的哈希函数
  return K%m;
}
int InitHash(HashTable& H, int size)
{
    H.rcd = (Node**)malloc(size*sizeof(Node*));
    if (H.rcd != NULL)
    {
        for (int i = 0; i < size; i++)
        {        
            H.rcd[i] = NULL;            
        }
        H.size = size;        
        H.count = 0;
        return 1;
    }
    else 
    {
        return 0;
    }
}
void HashTraverse(HashTable H)
{
    if (H.rcd != NULL)
    {
        Node* np, * nq;
        for (int i = 0; i < H.size; i++)
        {
            printf("%d:  ", i);
            if (H.rcd [i])
            {
                printf("%d  ",H.rcd [i]->r .key);
                Node* curr=H.rcd [i]->next;
                while (curr) {
                printf("-> %d  ", curr->r.key);
                curr=curr->next;
                }
                printf("\n");
            }
            else 
                printf("-\n");
        }
    }
}
int DestroyHash(HashTable& H)
{
    if (H.rcd != NULL)
    {
        Node* np, * nt;
        for (int i = 0; i < H.size; i++)
        {
            np = H.rcd[i];
            while (np != NULL)
            {
                nt = np;
                np = np->next;
                free(nt);
            }
        }
        H.size = 0;
        H.count = 0;
        return 1;
    }
    else
    {
        return 0;
    }
}

编程要求

本关的编程任务是补全函数来分别实现哈希表的查找、插入操作。

点击查看代码
Node* SearchHash(HashTable H, KeyType key);                        //查找
 int InsertHash(HashTable& H, ElemType e);                            //插入

测试说明

平台会对你编写的代码进行测试:
样例输入:
7
9
19 56 23 14 68 82 70 36 91
输入说明:
第一行输入7,表示输入哈希表的表长
第二行输入9,表示输入数据的个数
第三行输入9个整数,表示输入具体的数据
样例输出:
采用链地址法得到的哈希表为:
0: 91 -> 70 -> 14 -> 56
1: 36
2: 23
3: -
4: -
5: 82 -> 68 -> 19
6: -
输出说明:
输出采用除留余数法加链地址法建立哈希表,输出n个独立链表。

开始你的任务吧,祝你成功

点击查看代码
#include <stdio.h>
#include <stdlib.h>
#define N 20  // 数据元素个数
typedef int KeyType; // 设关键字域为整型
struct ElemType // 数据元素类型
{
   KeyType key;     
};
typedef struct Node {                 //链地址,其实这个指针就是链表
	ElemType r;
	struct Node* next;
}Node;
typedef struct {
	Node** rcd;								//(指向指针的指针)存放指针的数组
	int size;								//哈希表的容量
	int count;								//当前表中含有的记录个数	
}HashTable;

int InitHash(HashTable& H, int size);	//初始化哈希表
int DestroyHash(HashTable& H);									//销毁哈希表
Node* SearchHash(HashTable H, KeyType key);							//查找
int InsertHash(HashTable& H, ElemType e);							//插入
int DeleteHash(HashTable& H, KeyType key, ElemType& e);			//删除
void HashTraverse(HashTable H);										//遍历
unsigned Hash(KeyType K,int m);

int main()
{
    HashTable h;
	KeyType k;
	ElemType r[N]={0};   
	int m,n,i,j;  
   scanf("%d",&m); // H(key) = key % m
   scanf("%d",&n); // 数据的个数  
   InitHash(h,m);  
   for(i=0;i<n;i++)
   {
   	scanf("%d",&r[i].key);     
   }
   for(i=0;i<n;i++)
   { // 插入前N-1个记录     
		InsertHash(h, r[i]);		
   }
    printf("采用链地址法得到的哈希表为:\n");   	
	HashTraverse(h);	
	DestroyHash(h);
}
unsigned Hash(KeyType K,int m)
{ // 一个简单的哈希函数
  return K%m;
}

int InitHash(HashTable& H, int size)
{
	H.rcd = (Node**)malloc(size*sizeof(Node*));
	if (H.rcd != NULL)
	{
		for (int i = 0; i < size; i++)
		{		
			H.rcd[i] = NULL;			
		}
		H.size = size;		
		H.count = 0;
		return 1;
	}
	else 
	{
		return 0;
	}
}
int DestroyHash(HashTable& H)
{
	if (H.rcd != NULL)
	{
		Node* np, * nt;
		for (int i = 0; i < H.size; i++)
		{
			np = H.rcd[i];
			while (np != NULL)
			{
				nt = np;
				np = np->next;
				free(nt);
			}
		}
		H.size = 0;
		H.count = 0;
		return 1;
	}
	else
	{
		return 0;
	}
}
Node* SearchHash(HashTable H, KeyType key)
{
	/*********BEGIN*********/
	    int p;
    Node* np;
    p = Hash(key,H.size);    
    np = H.rcd[p];
    while (np)
    {
        if(key == np->r.key)
                return np ;
        np = np->next;
    }
    return NULL;

	/**********END**********/
}

int InsertHash(HashTable& H, ElemType e)
{ 
	/*********BEGIN*********/	
	    int p;
   Node* np;
   np = SearchHash(H, e.key);
   if (np == NULL)
   {
       p = Hash(e.key,H.size);
       np = (Node*)malloc(sizeof(Node));
       np->r = e;
       np->next = H.rcd[p];
       H.rcd[p] = np;
       H.count++;
       return 1;
   }
   else
   {
       return 0;
   }
	
	/**********END**********/
}

void HashTraverse(HashTable H)
{
	if (H.rcd != NULL)
	{
		Node* np, * nq;
		for (int i = 0; i < H.size; i++)
		{
			printf("%d:  ", i);
			if (H.rcd [i])
			{
				printf("%d  ",H.rcd [i]->r .key);
				Node* curr=H.rcd [i]->next;
				while (curr) {
                printf("-> %d  ", curr->r.key);
                curr=curr->next;
				}
				printf("\n");
			}
			else 
				printf("-\n");
		}
	}
}

第3关:直接插入排序

任务描述

本关任务:编写程序,已知待排序记录的关键字序列,利用直接插入排序进行非递减有序排序,要求输出每一趟排序的结果。

相关知识

直接插入排序算法思想

直接插入排序是一种最简单的排序方法,其基本操作是将一条记录插入到已排好序的有序表中,从而得到一个新的、记录数量增1的有序表。算法步骤如下:
1.设待排序的记录存放在数组r[1..n]中,r[1]是一个有序序列。
2.循环n-1次,每次使用顺序查找法,查找r[i](i=2,..,n)在已排好序的序列r[1..i-1]中的插入位置,然后将r[i]插入表长为i-1的有序序列r[1..i-1],直到将r[n]插入表长为n-1的有序序列r[1..n-1],最后得到一个表长为n的有序序列。
值得注意的是:在实现r[i]向前面的有序序列插入时,可以在r[0]处设置“哨兵”,令r[0]= r[i],这样,在查找插入位置的过程中不再需要考虑数组下标是否越界的问题。

随机数

rand()函数和 srand()函数
C语言要获得随机数,就要用到rand()函数。关于rand函数要注意以下几点:
1.要包含stdlib.h,rand函数声明在这个函数里。
2.rand()函数返回一个在 0 到 RAND_MAX 之间的整数值。
3.rand()产生的是伪随机数,如果不初始化,每次输出都是一样的序列。
4.srand(seed)是初始化函数,seed一般用机器时间做随机数的种子。
代码示例:

点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
int main()
{
   int i, n;
   n = 5;   
   /* 初始化随机数发生器 */
   srand( time(NULL)); 
   /* 输出 0 到 49 之间的 5 个随机数 */
   for( i = 0 ; i < n ; i++ ) {
      printf("%d\n", rand() % 50);
   }   
  return(0);
}
第一次运行结果: 35 7 25 3 43 第二次运行结果: 19 38 22 6 44 我们可以看到第一次运行结果和第二次运行的结果是不一样的。当我们想要第一次和第二次运行结果一致的话,可以传入一个相同的数字,也就是随机种子。具有相同随机种子的rand()函数生成的随机数是一样的。 示例:
点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
int main()
{
   int i, n;
   scanf("%d",&n);   
   /* 初始化随机数发生器 */
   srand(n); 
   /* 输出 0 到 49 之间的 5 个随机数 */
   for( i = 0 ; i < n ; i++ ) {
      printf("%d\n", rand() % 50);
   }   
  return(0);
}
第一次运行结果: 10 45 8 28 25 18 27 25 21 47 7 第二次运行结果: 10 45 8 28 25 18 27 25 21 47 7 待排序的数据用随机函数产生,以顺序表作为存储结构。 ### 待排序数据的存储结构 待排序的数据以顺序表作为存储结构,类型定义如下:
点击查看代码
typedef char* InfoType;
typedef struct {
    int key;//关键字项
    InfoType otherinfo;//其他数据项
}RedType;//记录类型                         
typedef struct {
    RedType r[MAXSIZE+1];//r[0]闲置或用做哨兵单元
    int length;//顺序表长度
}SqList;//顺序表类型
将待排序记录依次插入顺序表L函数定义如下:
点击查看代码
void InsertSqList(SqList &L) {
    int n;//待排序记录的个数
    cin>>n;
    srand(n);
    if(n > MAXSIZE) exit(ERROR);
    for(int i=1; i<=n; ++i) {
        L.r[i].key = rand() % 100;
        ++L.length;
    }
}

编程要求

根据提示,在右侧编辑器补充代码,编写对顺序表L做直接插入排序算法的函数,要求调用show函数打印每一趟排序的结果。
void InsertSort(SqList &L); //对顺序表L做直接插入排序

测试说明

平台会对你编写的代码进行测试:
测试输入:
10
输入说明:
第一行为整数n,代表待排序的整数的个数,同时也代表随机数的种子,系统自动产生n个整数。
预期输出:
95 8 78 25 18 77 75 71 47 7 //随机函数产生的待排序序列
8 95 78 25 18 77 75 71 47 7 //第1趟排序结果
8 78 95 25 18 77 75 71 47 7 //第2趟排序结果
8 25 78 95 18 77 75 71 47 7 //第3趟排序结果
8 18 25 78 95 77 75 71 47 7 //第4趟排序结果
8 18 25 77 78 95 75 71 47 7 //第5趟排序结果
8 18 25 75 77 78 95 71 47 7 //第6趟排序结果
8 18 25 71 75 77 78 95 47 7 //第7趟排序结果
8 18 25 47 71 75 77 78 95 7 //第8趟排序结果
7 8 18 25 47 71 75 77 78 95 //第9趟排序结果
输出说明:
输出结果总计n行,第一行为原始数据,从第二行到第n行为每一趟直接插入排序的结果。

开始你的任务吧,祝你成功!

点击查看代码
//直接插入排序
#include<iostream>
#include<fstream>
using namespace std;
#include <time.h>
#define MAXSIZE 100 //顺序表的最大长度
#define OK 0
#define ERROR -1
typedef char* InfoType;
typedef struct {
	int key;//关键字项
	InfoType otherinfo;//其他数据项
}RedType;//记录类型                         
typedef struct {
    RedType r[MAXSIZE+1];//r[0]闲置或用做哨兵单元
    int length;//顺序表长度
}SqList;//顺序表类型

//初始化一个空的顺序表L
void InitSqList(SqList &L) {
	L.length = 0;
}

//将待排序记录依次插入顺序表L
void InsertSqList(SqList &L) {
	int n;//待排序记录的个数
	cin>>n;
    srand(n);
	if(n > MAXSIZE) exit(ERROR);
	for(int i=1; i<=n; ++i) {
		L.r[i].key = rand() % 100;
		++L.length;
	}
}

//打印顺序表L
void show(SqList L) {
	for(int i=1; i<=L.length; ++i)
		cout<<L.r[i].key<<" ";
	cout<<endl;
}

//对顺序表L做直接插入排序
//要求调用show函数打印每一趟排序的结果
void InsertSort(SqList &L) {
	show(L);//打印初始待排序序列
	/*-------------代码开始------------------*/
     for (int i = 2; i <= L.length; ++i) {
        if (L.r[i].key < L.r[i - 1].key) {
            L.r[0] = L.r[i]; //设置哨兵
            int j;
            for (j = i - 1; L.r[0].key < L.r[j].key; --j) {
                L.r[j + 1] = L.r[j]; //记录后移
            }
            L.r[j + 1] = L.r[0]; //插入到正确位置
        }
        show(L); //打印每趟排序结果
    }
    /*-------------代码结束------------------*/
}

int main() {
	
	SqList L;
	InitSqList(L);
	InsertSqList(L);  //测试数据
    
	InsertSort(L);
	return OK;
}

第4关:简单选择排序

任务描述

本关任务:编写程序,已知待排序记录的关键字序列,利用简单选择排序进行非递减有序排序,要求输出每一趟排序的结果。

相关知识

简单选择排序算法思想

简单选择排序也称作直接选择排序,算法步骤如下:
1.设待排序的记录存放在数组r[1..n]中。第一趟从r[1]开始,通过n-1次比较,从n个记录中选出关键字最小的记录,即为r[k],交换r[1]和r[k]。
2.第二趟从r[2]开始,通过n-2次比较,从n-1个记录中选出关键字最小的记录,即为r[k],交换r[2]和r[k]。
3.依次类推,第i趟从r[i]开始,通过n-i次比较,从n-i+1个记录中选出关键字最小的记录,即为r[k],交换r[i]和r[k]。
4.经过n-1趟,排序完成。

随机数

rand()函数和 srand()函数
C语言要获得随机数,就要用到rand()函数。关于rand函数要注意以下几点:
1.要包含stdlib.h,rand函数声明在这个函数里。
2.rand()函数返回一个在 0 到 RAND_MAX 之间的整数值。
3.rand()产生的是伪随机数,如果不初始化,每次输出都是一样的序列。
4.srand(seed)是初始化函数,seed一般用机器时间做随机数的种子。
代码示例:

点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
int main()
{
   int i, n;
   n = 5;   
   /* 初始化随机数发生器 */
   srand( time(NULL)); 
   /* 输出 0 到 49 之间的 5 个随机数 */
   for( i = 0 ; i < n ; i++ ) {
      printf("%d\n", rand() % 50);
   }   
  return(0);
}
第一次运行结果: 35 7 25 3 43 第二次运行结果: 19 38 22 6 44 我们可以看到第一次运行结果和第二次运行的结果是不一样的。当我们想要第一次和第二次运行结果一致的话,可以传入一个相同的数字,也就是随机种子。具有相同随机种子的rand()函数生成的随机数是一样的。 示例:
点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
int main()
{
   int i, n;
   scanf("%d",&n);   
   /* 初始化随机数发生器 */
   srand(n); 
   /* 输出 0 到 49 之间的 5 个随机数 */
   for( i = 0 ; i < n ; i++ ) {
      printf("%d\n", rand() % 50);
   }   
  return(0);
}
第一次运行结果: 10 45 8 28 25 18 27 25 21 47 7 第二次运行结果: 10 45 8 28 25 18 27 25 21 47 7 待排序的数据用随机函数产生,以顺序表作为存储结构。 ### 待排序数据的存储结构 待排序的数据以顺序表作为存储结构,类型定义如下:
点击查看代码
typedef char* InfoType;
typedef struct {
    int key;//关键字项
    InfoType otherinfo;//其他数据项
}RedType;//记录类型                         
typedef struct {
    RedType r[MAXSIZE+1];//r[0]闲置或用做哨兵单元
    int length;//顺序表长度
}SqList;//顺序表类型
将待排序记录依次插入顺序表L函数定义如下:
点击查看代码
void InsertSqList(SqList &L) {
    int n;//待排序记录的个数
    cin>>n;
    srand(n);
    if(n > MAXSIZE) exit(ERROR);
    for(int i=1; i<=n; ++i) {
        L.r[i].key = rand() % 100;
        ++L.length;
    }
}

编程要求

根据提示,在右侧编辑器补充代码,编写对顺序表L做简单选择排序算法的函数,要求调用show函数打印每一趟排序的结果。
void SelectSort(SqList &L); //对顺序表L做简单选择排序

测试说明

平台会对你编写的代码进行测试:
测试输入:
10
输入说明:
第一行为整数n,代表待排序的整数的个数,同时也代表随机数的种子,系统自动产生n个整数。
预期输出:
95 8 78 25 18 77 75 71 47 7 //随机函数产生的待排序序列
7 8 78 25 18 77 75 71 47 95 //第1趟排序结果
7 8 78 25 18 77 75 71 47 95 //第2趟排序结果
7 8 18 25 78 77 75 71 47 95 //第3趟排序结果
7 8 18 25 78 77 75 71 47 95 //第4趟排序结果
7 8 18 25 47 77 75 71 78 95 //第5趟排序结果
7 8 18 25 47 71 75 77 78 95 //第6趟排序结果
7 8 18 25 47 71 75 77 78 95 //第7趟排序结果
7 8 18 25 47 71 75 77 78 95 //第8趟排序结果
7 8 18 25 47 71 75 77 78 95 //第9趟排序结果
输出说明:
输出结果总计n行,第一行为原始数据,从第二行到第n行为每一趟简单选择排序的结果。

开始你的任务吧,祝你成功!

点击查看代码
//简单选择排序
#include<iostream>
#include<fstream>
using namespace std;
#include <time.h>
#define MAXSIZE 100 //顺序表的最大长度
#define OK 0
#define ERROR -1
typedef char* InfoType;
typedef struct {
	int key;//关键字项
	InfoType otherinfo;//其他数据项
}RedType;//记录类型                         
typedef struct {
    RedType r[MAXSIZE+1];//r[0]闲置或用做哨兵单元
    int length;//顺序表长度
}SqList;//顺序表类型

//初始化一个空的顺序表L
void InitSqList(SqList &L) {
	L.length = 0;
}

//将待排序记录依次插入顺序表L
void InsertSqList(SqList &L) {
	int n;//待排序记录的个数
	cin>>n;
    srand(n);
	if(n > MAXSIZE) exit(ERROR);
	for(int i=1; i<=n; ++i) {
		L.r[i].key = rand() % 100;
		++L.length;
	}
}

//打印顺序表L
void show(SqList L) {
	for(int i=1; i<=L.length; ++i)
		cout<<L.r[i].key<<" ";
	cout<<endl;
}

//交换两个记录
void swap(RedType &a,RedType &b) {
	RedType temp;
	temp = a;
	a = b;
	b = temp;
}

//对顺序表L做简单选择排序
//要求调用show函数打印每一趟排序的结果
void SelectSort(SqList &L) {
	show(L);//打印初始待排序序列
	/*-------------代码开始------------------*/
    for (int i = 1; i < L.length; ++i) {
        int minIndex = i;
        for (int j = i + 1; j <= L.length; ++j) {
            if (L.r[j].key < L.r[minIndex].key) {
                minIndex = j;
            }
        }
        if (minIndex != i) {
            swap(L.r[i], L.r[minIndex]);
        }
        show(L); //打印每趟排序结果
    }
    /*-------------代码结束------------------*/
}

int main() {
	SqList L;
	InitSqList(L);
	InsertSqList(L);
	SelectSort(L);
	return OK;
}

第5关:快速排序

任务描述

本关任务:编写程序,已知待排序记录的关键字序列,利用快速排序进行非递减有序排序,要求输出每一趟排序的结果。

相关知识

快速排序的算法思想

快速排序的基本策略是:在待排序的n个记录中任取一个记录(通常取第一个记录)作为枢轴(或支点),设其关键字为pivotkey。经过一趟排序后,把所有关键字小于pivotkey的记录交换到前面,把所有关键字大于pivotkey的记录交换到后面,结果将待排序记录分成两个子表,最后将枢轴放置在分界处的位置。然后,分别对左、右子表重复上述过程,直至每一子表只有一个记录时,排序完成。
其中,一趟快速排序的基本步骤如下:
1.选择待排序表中的第一个记录作为枢轴,将枢轴记录暂存在r[0]的位置上。附设两个指针low和high,初始时分别指向表的下界和上界(第一趟时,low=1,high=L.length)。
2.从表的最右侧位置依次向左搜索,找到第一个关键字小于枢轴关键字pivotkey的记录,将其移到low处。
3.然后再从表的最左侧位置依次向右搜索,找到第一个关键字大于枢轴关键字pivotkey的记录,将其移到high处。
4.重复步骤2和3,直至low和high相等为止。此时low或high的位置即为枢轴在排序中的最终位置,原表被分成两个子表。

编程要求

根据提示,在右侧编辑器补充代码,编写对顺序表L做快速排序算法的函数,要求调用show函数打印每一趟排序的结果。
int Partition(SqList &L,int low,int high) ; / /以L.r[low]作为枢轴,对顺序表L中的子序列L.r[low..high]进行划分,返回枢轴的位置
void QSort(SqList &L,int low,int high) ; / /对顺序表L中的序列L.r[low..high]做快速排序

测试说明

平台会对你编写的代码进行测试:
测试输入:
10
输入说明:
第一行为整数n,代表待排序的整数的个数,同时也代表随机数的种子,系统自动产生n个整数插入顺序表中。
预期输出:
95 8 78 25 18 77 75 71 47 7 //随机函数产生的待排序序列
7 8 78 25 18 77 75 71 47 95 //以95为轴一次划分排序的结果
7 8 78 25 18 77 75 71 47 95 //以7为轴一次划分排序的结果
7 8 78 25 18 77 75 71 47 95 //以8为轴一次划分排序的结果
7 8 47 25 18 77 75 71 78 95 //以78为轴一次划分排序的结果
7 8 18 25 47 77 75 71 78 95 //以47为轴一次划分排序的结果
7 8 18 25 47 77 75 71 78 95 //以18为轴一次划分排序的结果
7 8 18 25 47 71 75 77 78 95 //以77为轴一次划分排序的结果
7 8 18 25 47 71 75 77 78 95 //以71为轴一次划分排序的结果
输出说明:
输出结果总计k+1行,第一行为原始数据,k代表快速排序的趟数,从第二行到第k+1行为每一趟快速排序的结果。

开始你的任务吧,祝你成功!

点击查看代码
//快速排序
#include<iostream>
#include<fstream>
using namespace std;
#include <time.h>
#define MAXSIZE 100 //顺序表的最大长度
#define OK 0
#define ERROR -1
typedef char* InfoType;
typedef struct {
	int key;//关键字项
	InfoType otherinfo;//其他数据项
}RedType;//记录类型                         
typedef struct {
    RedType r[MAXSIZE+1];//r[0]闲置或用做哨兵单元
    int length;//顺序表长度
}SqList;//顺序表类型

//初始化一个空的顺序表L
void InitSqList(SqList &L) {
	L.length = 0;
}

//将待排序记录依次插入顺序表L
void InsertSqList(SqList &L) {
	int n;//待排序记录的个数
	cin>>n;
    srand(n);
	if(n > MAXSIZE) exit(ERROR);
	for(int i=1; i<=n; ++i) {
		L.r[i].key = rand() % 100;
		++L.length;
	}
}


//打印顺序表L
void show(SqList L) {
	for(int i=1; i<=L.length; ++i)
		cout<<L.r[i].key<<" ";
	cout<<endl;
}

//对顺序表L中的子序列L.r[low..high]进行划分,返回枢轴的位置
//以L.r[low]作为枢轴
int Partition(SqList &L,int low,int high) {
	/*-------------代码开始------------------*/
    int pivot = L.r[low].key; // 选择第一个元素作为枢轴
    while (low < high) {
        while (low < high && L.r[high].key >= pivot) --high;
        L.r[low] = L.r[high]; // 将比枢轴小的元素移到低端
        while (low < high && L.r[low].key <= pivot) ++low;
        L.r[high] = L.r[low]; // 将比枢轴大的元素移到高端
    }
    L.r[low].key = pivot; // 枢轴元素放到最终位置
    return low; // 返回枢轴的位置
    /*-------------代码结束------------------*/
}

//对顺序表L中的子序列L.r[low..high]做快速排序
//要求调用show函数打印每一趟划分的结果
void QSort(SqList &L,int low,int high) {
	/*-------------代码开始------------------*/
    if (low < high) {
        int pivotPos = Partition(L, low, high); // 划分
        show(L); // 打印当前划分结果
        QSort(L, low, pivotPos - 1); // 递归排序左子序列
        QSort(L, pivotPos + 1, high); // 递归排序右子序列
    }
    /*-------------代码结束------------------*/
}

//对顺序表L做快速排序
void QuickSort(SqList &L) {
	show(L);//打印初始待排序序列
	QSort(L,1,L.length);
}

int main() {
	SqList L;
	InitSqList(L);
	InsertSqList(L);
	QuickSort(L);
	return OK;
}

第6关:堆排序

任务描述

本关任务:编写程序,已知待排序记录的关键字序列,利用堆排序进行非递减有序排序,要求输出每一趟排序的结果。

相关知识

堆排序列用了大根堆(小根堆)堆顶记录的关键字最大(最小)这一特征,使得当前无序的序列中选择关键字最大(最小)的记录变得简单。下面以大根堆为例,堆排序的步骤如下:
1.按堆的定义将待排序序列r[1..n]调整为大根堆(这个过程称为建初堆),交换r[1]和r[n],则r[n]为关键字最大的记录。
2.将r[1..n-1]重新调整为堆,交换r[1]和r[n-1],则r[n-1]为关键字次大的记录。
3.循环n-1次,直到交换了r[1]和r[2]为止,得到了一个非递减的有序序列r[1..n]。

编程要求

根据提示,在右侧编辑器补充代码,编写实现对顺序表L做堆排序的函数。
void HeapAdjust(SqList &L,int s,int m) ; / /将L.r[s..m]调整为以L.r[s]为根的大根堆
void CreateHeap(SqList &L); / /把无序序列L.r[1..L.length]建成大根堆
void HeapSort(SqList &L); / /对顺序表L进行堆排序

测试说明

平台会对你编写的代码进行测试:
测试输入:
10
输入说明:
第一行为整数n,代表待排序的整数的个数,同时也代表随机数的种子,系统自动产生n个整数插入顺序表中。
预期输出:
95 8 78 25 18 77 75 71 47 7 //随机函数产生的待排序序列
95 71 78 47 18 77 75 25 8 7 //将原始序列建成大顶堆,堆长度为10
78 71 77 47 18 7 75 25 8 95 //将堆顶元素和堆中最后一个记录交换,然后再调整堆的结果,堆长度为9
77 71 75 47 18 7 8 25 78 95 //堆长度为8
75 71 25 47 18 7 8 77 78 95 //堆长度为7
71 47 25 8 18 7 75 77 78 95 //堆长度为6
47 18 25 8 7 71 75 77 78 95 //堆长度为5
25 18 7 8 47 71 75 77 78 95 //堆长度为4
18 8 7 25 47 71 75 77 78 95 //堆长度为3
8 7 18 25 47 71 75 77 78 95 //堆长度为2
7 8 18 25 47 71 75 77 78 95 //堆长度为1
输出说明:
输出结果总计n+1行,第一行为原始数据,第二行为建初堆结果,从第三行到第n+1行为每一趟将堆顶元素和堆中最后一个记录交换输出,然后再调整堆的结果。

开始你的任务吧,祝你成功!

点击查看代码
#include<iostream>
#include<fstream>
using namespace std;
#include <time.h>
#define MAXSIZE 100 //顺序表的最大长度
#define OK 0
#define ERROR -1
typedef char* InfoType;
typedef struct {
    int key;//关键字项
    InfoType otherinfo;//其他数据项
}RedType;//记录类型                         
typedef struct {
    RedType r[MAXSIZE+1];//r[0]闲置或用做哨兵单元
    int length;//顺序表长度
}SqList;//顺序表类型

//初始化一个空的顺序表L
void InitSqList(SqList &L) {
    L.length = 0;
}

//将待排序记录依次插入顺序表L
void InsertSqList(SqList &L) {
    int n;//待排序记录的个数
    cin>>n;
    srand(n);
    if(n > MAXSIZE) exit(ERROR);
    for(int i=1; i<=n; ++i) {
        L.r[i].key = rand() % 100;
        ++L.length;
    }
}

//打印顺序表L
void show(SqList L) {
    for(int i=1; i<=L.length; ++i)
        cout<<L.r[i].key<<" ";
    cout<<endl;
}

//交换两个记录
void swap(RedType &a,RedType &b) {
    RedType temp;
    temp = a;
    a = b;
    b = temp;
}

//假设L.r[s+1..m]已经是大根堆,将L.r[s..m]调整为以L.r[s]为根的大根堆
void HeapAdjust(SqList &L,int s,int m) {
    int j = 2 * s; // 左孩子节点位置
    while (j <= m) {
        if (j < m && L.r[j].key < L.r[j + 1].key) { // 右孩子存在且大于左孩子
            j++; // 指向右孩子
        }
        if (L.r[s].key >= L.r[j].key) break; // 父节点大于等于子节点,不需要调整
        swap(L.r[s], L.r[j]); // 交换父节点和较大的子节点
        s = j; // 继续向下调整
        j = 2 * s; // 更新左孩子节点位置
    }
}

//把无序序列L.r[1..L.length]建成大根堆
void CreateHeap(SqList &L) {
    for (int i = L.length / 2; i >= 1; --i) { // 从最后一个非叶子节点开始向上调整
        HeapAdjust(L, i, L.length);
    }
}

//对顺序表L进行堆排序
void HeapSort(SqList &L) {
    show(L); // 打印初始待排序序列
    CreateHeap(L); // 建初堆
    show(L); // 打印建初堆结果
    for (int i = L.length; i > 1; --i) {
        swap(L.r[1], L.r[i]); // 将堆顶元素和堆中最后一个记录交换
        HeapAdjust(L, 1, i - 1); // 调整剩余部分为大根堆
        show(L); // 打印每一趟调整后的结果
    }
}

int main() {
    SqList L;
    InitSqList(L);
    InsertSqList(L);
    HeapSort(L);
    return OK;
}

posted on 2024-12-15 11:21  百瑜  阅读(275)  评论(0)    收藏  举报