第九、十章 查找和排序
(部分内容参考《数据结构教程》第五版[李春葆主编])
一、基本知识点
(1)顺序查找算法及其性能分析。
(2)折半查找算法及其性能分析。
(3)索引存储结构和分块查找的特点及其性能分析。
(4)二叉排序树的查找和插入算法设计,删除过程。
(5)平衡二叉树的调整过程和性能分析。
(6)B-树的查找、插人和删除过程。
(7)哈希表的特点、哈希函数的构造方法和解决冲突的方法。
(8)各种查找方法的特点和性能对比分析。
(1)内排序相关概念和排序算法分析方法。
(2)简单类排序算法(直接插入、冒泡排序和简单选择排序)设计。
(3)折半插入排序算法设计。
(4)希尔排序算法设计。
(5)快速排序算法设计。
(6)堆排序算法设计。
(7)二路归并排序算法设计。
(8)基数排序算法设计。
(9)各种内排序算法的特点及其应用。
二、要点归纳、基本实现
(1)在查找表中,所有记录的关键字是唯一的。查找表分为静态查找表和动态查找表。
(2)衡量查找算法性能的主要指标是平均查找长度(ASL),分为成功和不成功平均查找长度。
(3)在对线性表进行顺序查找时,线性表既可以采用顺序存储,也可以采用链式存储。
(4)在对线性表进行折半查找时,要求线性表必须以顺序方式存储,且元素按关键字有序排列。
(5)在对含有n个元素的有序顺序表进行顺序查找时,算法时间复杂度仍然为O(n)。
(6)在对含有n个元素的有序顺序表进行折半查找时,算法时间复杂度是按县O(log2n)。
(7)折半查找的判定树反映了所有可能的查找情况,内部结点对应查找成功,外部结点对应查找失败。若内部结点个数为n,则外部结点恰好有n+1个。
(8)在对含有n个元素的有序顺序表进行折半查找时,成功和和不成功情况下关键字比较的最多次数为「log2(n+1)](向上取)。
(9)分块查找在等概率情况下,其平均查找长度不仅与表长有关,而且与每一块中的元素个数有关。分块查找算法的效率介于顺序查找和折半查找之间。
(10)向一棵二叉排序树中插入一个结点所需的关键字比较次数最多是树的高度。
(11)向一棵二叉排序树中插入一个结点均是以叶子结点插人的。
(12)若先删除二叉排序树中的某个结点,再重新插入该结点,不一定得到原来的二叉排序树。
(13)二叉排序树的中序序列是一个递增有序序列。
(14)平衡二叉树的查找过程和二叉排序树的查找过程相同。插入过程是先采用二叉排序树的方法插入关键字k,若不平衡则需要调整,调整分为LL、RR、LR和RL类型。
(15)给定结点个数的平衡二叉树的高度不一定是唯一的。
(16)哈希表不同于其他存储方法,它根据元素的关键字直接计算出该元素的存储地址。这个地址计算函数就是哈希函数。
(17)设计哈希表主要是设计哈希函数和哈希冲突解决方法。
(18)同义词是指两个不同关键字的元素,其哈希函数值相同,这种冲突称为同义词冲突。非同义词冲突是指哈希函数值不相同的两个元素争夺同一个后继哈希地址,导致出现堆积(或聚集)现象。
(19)在采用线性探测法处理冲突的哈希表中,所有同义词在表中不一定相邻。
(20)在理想的情况下(如元素个数和元素值确定,可以设计出不出现冲突的哈希函数),在哈希表中查找一个元素的时间复杂度为O(1)。
(1)稳定的排序算法是指多个相同关键字记录在排序后相对位置不发生改变。
(2)内排序的主要时间花在关键字的比较和记录的移动上。
(3)直接插入排序在初始数据正序时呈现最好情况,此时时间复杂度为O(n);在初始数据反序时呈现最坏情况,此时时间复杂度为O(n2)。平均情况接近最坏情况。
(4)直接插入排序和折半插入排序每趟产生的有序区是局部有序区。
(5)折半插入排序采用折半查找方法找插入记录的位置,但和直接插入排序移动记录的次数是相同的。
(6)希尔排序是利用了直接插入排序在数据正序时呈现最好情况这个特点。
(7))冒泡排序在初始数据正序时呈现最好情况,此时时间复杂度为O(n);在初始数据反序时呈现最坏情况,此时时间复杂度为O(n2)。平均情况接近最坏情况。
(8)冒泡排序每趟产生的有序区是全部有序区。每趟将一个记录归位,以后该记录位置不再发生改变。
(9)快速排序利用划分方法实现排序,将一个无序区分为两个子无序区,每个子无序区的处理是独立的,哪个先处理都可以。
(10)快速排序每趟将一个记录归位,以后该记录位置不再发生改变,但每趟并不产生有序区。
(11)快速排序在初始数据正序和反序时都呈现最坏情况,而平均情况接近最好情况。快速排序的空间复杂度为O(log2n)。
(12)选择类排序算法(包括简单选择排序和堆排序)与初始数据的正反序无关,它们都是不稳定的。
(13)简单选择排序采用简单比较方法从k个记录中挑选出最大(或者最小)关键字记录,执行时间为O(k)。而堆排序是采用堆结构来实现的,执行时间为O(log2k)。
(14)简单选择排序的平均情况接近最坏情况,而堆排序的平均情况接近最好情况。
(15)在一个大根堆中,根结点是关键字最大的结点,关键字最小的结点一定属于叶子结点。从根结点到某个叶子结点的路径恰好构成一个关键字递减序列。
(16)堆的用途是挑选最大(或者最小)关键字记录,当需要从一组记录中连续挑选最大(或者最小)关键字记录时可以采用堆来实现。对一个堆进行层次遍历,不一定能得到一个有序序列。
(17)二路归并排序的空间复杂度为O(n),它各种内排序中空间复杂度最高的排序方法。
(18)三路归并排序的时间复杂度也是O(nlog2n),但其算法设计远比二路归并排序算法复杂。
(19)基数排序不需要关键字比较,其空间复杂度为O®。
(20)基数排序的每一趟都是稳定的,所以基数排序是稳定的排序方法。
(21)在多个关键字排序中可能需要考虑排序算法的稳定性。
(22)不能简单地认为一种排序方法就一定好于另外一种排序方法,影响排序性能的因素有多个。
1、各种查找算法实现
(1)顺序查找:使用数组或链表结构。用随机函数生成16个不重复的字母(’a’~’z’),键盘输入待查找的字母,返回查找成功与否,若成功则返回该字母所在的位置(序号),并计算比较次数。
(2)折半查找:用数组实现,查找前元素先排序。计算比较次数。分别用查找成功、不成功进行测试。
(3)二叉查找树:手工输入10个字母,生成一棵二叉查找树,用递归算法打印树结构或分别输出先序和中序遍历序列以确认其结构。键盘输入待查找的字母,计算比较次数。分别用查找成功、不成功进行测试。
MySearch.h
#pragma once
#include<iostream>
#include<vector>
#include<algorithm>
#include<set>
#include<ctime>
using namespace std;
//(1)顺序查找:使用数组或链表结构。用随机函数生成16个不重复的字母(’a’~’z’),
//键盘输入待查找的字母,返回查找成功与否,若成功则返回该字母所在的位置(序号),并计算比较次数。
//
//(2)折半查找:用数组实现,查找前元素先排序。计算比较次数。分别用查找成功、不成功进行测试。
//
//(3)二叉查找树:手工输入10个字母,生成一棵二叉查找树,用递归算法打印树结构或分别输出先序和中序遍历序列以确认其结构。
//键盘输入待查找的字母,计算比较次数。分别用查找成功、不成功进行测试。
const int Max = 30;
typedef char KeyType;
typedef char InfoType;
typedef struct
{
KeyType key;
InfoType data;
}RecType;
typedef struct node
{
KeyType key;
InfoType data;
struct node * lchild, * rchild;
}BSTNode;
void CreateList(RecType R[], KeyType keys[], int n);
void DispList(RecType R[], int n);
int SeqSearch(RecType R[], int n, KeyType k); //顺序查找
int BinSearch(RecType R[], int n, KeyType k); //折半查找
bool InsertBST(BSTNode*& bt, KeyType k); //插入结点,由CreateBST调用
BSTNode* CreateBST(KeyType A[], int n); //通过数组A[]创建二叉查找树
BSTNode* SearchBST(BSTNode* bt, KeyType k, int& count); // 查找,递归实现
void DispBST(BSTNode* bt); //输出二叉查找树
void PreOrder(BSTNode* bt);//前序遍历
void InOrder(BSTNode* bt);//中序遍历
MySearch.cpp
#include"MySearch.h"
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++)
{
cout << R[i].key<< " ";
}
cout << endl;
}
int SeqSearch(RecType R[], int n, KeyType k)
{
int i = 0;
int count = 1;
while (i<n && R[i].key!=k)
{
cout << R[i].key << " ";
count++;
i++;
}
if (i >= n)
{
cout << endl;
return 0;
}
else
{
cout << R[i].key << " ";
cout << "\n比较次数为:" << count << endl;
return i + 1;
}
}
int BinSearch(RecType R[], int n, KeyType k)
{
int first = 0, last = n - 1, mid, count = 0;
while (first<=last)
{
mid = (first + last) / 2;
cout << "第" << (++count) << "次比较:在[" << first << "," << last
<< "]中比较元素R[" << mid << "] : " << R[mid].key << endl;
if (R[mid].key == k)
{
return mid + 1;
}
if (R[mid].key>k)
{
last = mid - 1;
}
else
{
first = mid + 1;
}
}
return 0;
}
bool InsertBST(BSTNode*& bt, KeyType k) //插入结点,由CreateBST调用
{
if (bt == NULL)
{
bt = (BSTNode*)malloc(sizeof(BSTNode));
bt->key = k;
bt->lchild = bt->rchild = NULL;
return true;
}
else if (k == bt->key)
{
return false;
}
else if (k < bt->key)
{
return InsertBST(bt->lchild, k); //插入到左子树
}
else
{
return InsertBST(bt->rchild, k); //插入到右子树
}
}
BSTNode* CreateBST(KeyType A[], int n) //通过数组A[]创建二叉查找树
{
BSTNode* bt = NULL; //初始是空数
int i = 0;
while (i<n)
{
if (InsertBST(bt, A[i])==1)
{
cout << "第" << i + 1 << "次,插入" << A[i]<<" 此时树为:";
DispBST(bt);
cout << endl;
i++;
}
}
return bt;
}
void DispBST(BSTNode* bt) //输出二叉查找树
{
if (bt != NULL)
{
cout << bt->key;
if (bt->lchild != NULL || bt->rchild != NULL)
{
cout << "(";
DispBST(bt->lchild);
if (bt->rchild != NULL)
{
cout << ",";
}
DispBST(bt->rchild);
cout << ")";
}
}
}
BSTNode* SearchBST(BSTNode* bt, KeyType k, int &count) // 查找,递归实现
{
count++;
if (!bt || k == bt->key)
{
return bt;
}
else if (k < bt->key)
{
return SearchBST(bt->lchild, k, count);
}
else return SearchBST(bt->rchild, k, count);
}
void PreOrder(BSTNode* bt)//前序遍历
{
if (bt)
{
cout << bt->key <<" ";
PreOrder(bt->lchild);
PreOrder(bt->rchild);
}
}
void InOrder(BSTNode* bt)//中序遍历
{
if (bt)
{
InOrder(bt->lchild);
cout << bt->key <<" ";
InOrder(bt->rchild);
}
}
MySearch_main.cpp
#include"MySearch.h"
int main()
{
srand(unsigned int(time(0)));
vector<char> alpha; //使用vector,方便折半的排序元素
set<char>judge; //使用set容器,因为它的特性是不允许容器中有重复的元素
while (alpha.size() < 16)//创建随机数
{
int temp = rand() % 26;
if (!judge.count(temp + 'a'))
{
alpha.push_back(temp + 'a');
judge.insert(temp + 'a');
}
}
cout << "随机生成的字母如下所示:\n";
for (auto c : alpha) cout << c << " ";
cout << endl;
char ch[16] = "";
for (int i = 0; i < alpha.size(); i++)
{
ch[i] = alpha[i];
}
KeyType k;
RecType R[Max];
int len = 16;
cout << "--------顺序查找----------" << endl;
CreateList(R, ch, len);
cout << "原序列:";
DispList(R, len);
cout << "请输入你要查找的字符:";
cin >> k;
int result = SeqSearch(R, len, k);
if (result != 0)
{
cout << "元素" << k << "在序列中的位置为:" << result << endl;
}
else
{
cout << "元素" << k << "不在此序列." << endl;
}
cout << "--------折半查找----------" << endl;
sort(alpha.begin(), alpha.end());
for (int i = 0; i < alpha.size(); i++)
{
ch[i] = alpha[i];
}
CreateList(R, ch, len);
cout << "原序列:";
DispList(R, len);
cout << "请输入你要查找的字符:";
cin >> k;
int result_2 = BinSearch(R, len, k);
if (result_2 != 0)
{
cout << "元素" << k << "在序列中的位置为:" << result_2 << endl;
}
else
{
cout << "元素" << k << "不在此序列."<< endl;
}
cout << "--------二叉查找树----------" << endl;
BSTNode* bt;
KeyType* arr = new KeyType[10]; //动态数组
for (int i = 0; i < 10; i++) //手动输入10个字符
{
cout << "请输入第"<<i+1<<"个字符:";
cin >> arr[i];
}
int n = 10;
cout << "创建二叉查找树:" << endl;
bt = CreateBST(arr, n);
cout << "先序遍历:" << endl;
PreOrder(bt);
cout << endl;
cout << "中序遍历:" << endl;
InOrder(bt);
cout << endl;
cout << "你想查找什么字符?\t";
cin >> k;
int count = 0;
if (SearchBST(bt, k, count))
{
cout << "查找字符"<< k <<"成功,比较次数:" << count;
}
else
{
cout << "查找字符" << k << "失败,比较次数:" << count;
}
delete[]arr; //释放动态数组
}
2、各种排序算法的实现
用随机函数生成16个2位正整数(10~99),实现插入排序、选择排序、冒泡排序、双向冒泡、快速排序、二路归并排序等多种排序算法,输出排序中间过程、统计关键字的比较次数和记录的移动次数。
MySort.h
#pragma once
#include<iostream>
using namespace std;
//用随机函数生成16个2位正整数(10~99),
//实现插入排序、选择排序、冒泡排序、双向冒泡、快速排序、二路归并排序等多种排序算法,
//输出排序中间过程、统计关键字的比较次数和记录的移动次数。
typedef int InfoType;
typedef int KeyType;
typedef struct
{
KeyType key;
InfoType data;
}RecType;
//建立线性表
void CreateList(RecType R[], KeyType keys[], int n);
//输出线性表
void DispList(RecType R[], int n);
//交换函数
void Swap(RecType& x, RecType& y);
//直接插入排序
void InsertSort(RecType R[], int n);
//折半插入排序
void BinInsertSort(RecType R[], int n);
//选择排序
void SelectSort(RecType R[], int n);
//冒泡排序
void BubbleSort(RecType R[], int n);
//双向冒泡
void ShakerSort(RecType R[], int n);
//快速排序
void disppart(RecType R[], int s, int t); //显示一趟划分后的结果
int partition(RecType R[], int s, int t); //一趟划分
void QuickSort(RecType R[], int s, int t); //对R[s..t]的元素进行快速排序
void Print();
//二路归并排序( 自底向上的过程 )
void Merge(RecType R[], int low, int mid, int high);
void MergePass(RecType R[], int length, int n);//对整个数序进行一趟归并
void MergeSort(RecType R[], int n);//自底向上的二路归并算法
void Print1();
//随机生成16个10-99的整数
void Random(int arr[], int n);
MySort.cpp
#include"MySort.h"
//建立线性表
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++)
{
cout<<R[i].key<<" ";
}
cout << endl;
}
//交换函数
void Swap(RecType& x, RecType& y)
{
RecType temp = x;
x = y;
y = temp;
}
//直接插入排序
void InsertSort(RecType R[], int n)
{
int i, j, move=0, compare=0;
RecType temp;
for ( i = 1; i < n; i++)
{
if (R[i - 1].key > R[i].key)
{
cout << " i=" << i << "\t将" << R[i].key << "插入到“有序区”,结果为:";
temp = R[i];
j = i - 1;
do //找R[i]的插入位置
{
R[j + 1] = R[j]; //将关键字大于R[i].key的记录后移
j--;
move++;
compare++;
} while (j>=0 and R[j].key>temp.key);
R[j + 1] = temp;//在j+1处插入R[i]
}
else {
compare++;
cout << " i=" << i << "\t此 时 没 有 反 序,不 移 动 :";
}
DispList(R, n); //每次改变一步输出一次结果
}
cout << "比较次数:" << compare << "\t移动次数:" << move<<endl;
}
//折半插入排序
void BinInsertSort(RecType R[], int n)
{
int i, j, low, high, mid, move=0, compare=0;
RecType temp;
for (i = 1; i < n; i++)
{
if (R[i].key < R[i - 1].key) //反序时
{
cout << " i=" << i << "\t将" << R[i].key << "插入到“有序区”,结果为:";
temp = R[i]; //将R[i]保存到tmp中
low = 0; high = i - 1;
while (low <= high) //在R[low..high]中查找插入的位置
{
compare++;
mid = (low + high) / 2; //取中间位置
if (temp.key < R[mid].key)
high = mid - 1; //插入点在左半区
else
low = mid + 1; //插入点在右半区
} //找位置high
for (j = i - 1; j >= high + 1; j--) //集中进行元素后移
{
R[j + 1] = R[j];
move++;
}
R[high + 1] = temp; //插入tmp
}
else {
compare++;
cout << " i=" << i << "\t此 时 没 有 反 序,不 移 动 :";
}
DispList(R, n);
}
cout << "比较次数:" << compare << "\t移动次数:" << move << endl;
}
//选择排序
void SelectSort(RecType R[], int n)
{
int i, j, k, move=0, compare=0;
for (i = 0; i < n - 1; i++) //做第i趟排序
{
k = i;
for (j = i + 1; j < n; j++) //在当前无序区R[i..n-1]中选key最小的R[k]
{
compare++;
if (R[j].key < R[k].key)
{
k = j; //k记下目前找到的最小关键字所在的位置
}
}
if (k != i) //交换R[i]和R[k]
{
Swap(R[i], R[k]);
move++;
}
cout << " i=" << i << "\t本次选择" << R[i].key << ",结果为:";
DispList(R, n);
}
cout << "比较次数:" << compare << "\t移动次数:" << move << endl;
}
//冒泡排序
void BubbleSort(RecType R[], int n)
{
int i, j, move = 0, compare = 0;
bool exchange;
for (i = 0; i < n - 1; i++)
{
exchange = false; //一趟前exchange置为假
for (j = n - 1; j > i; j--) //归位R[i],循环n-i-1次
{
compare++;
if (R[j].key < R[j - 1].key) //相邻两个元素反序时
{
Swap(R[j], R[j - 1]); //将这两个元素交换
move++;
exchange = true; //一旦有交换,exchange置为真
}
}
cout << " i=" << i << "\t"<<"归为元素"<<R[i].key<<",结果为:";
DispList(R, n);
if (!exchange) //本趟没有发生交换,中途结束算法
{
break;
}
}
cout << "比较次数:" << compare << "\t移动次数:" << move << endl;
}
//快速排序
int count1 = 0;
int compare = 0;
void disppart(RecType R[], int s, int t) //显示一趟划分后的结果
{
static int i = 1;
int j;
cout << "第" << i << "次划分:";
for (j = 0; j < s; j++)
{
cout << " ";
}
for (j = s; j <=t; j++)
{
cout << " "<<R[j].key;
}
cout << endl;
i++;
}
int partition(RecType R[], int s, int t) //一趟划分
{
int i = s, j = t;
RecType tmp = R[i]; //以R[i]为基准
while (i < j) //从两端交替向中间扫描,直至i=j为止
{
while (j > i && R[j].key >= tmp.key)
{
j--; //从右向左扫描,找一个小于tmp.key的R[j]
compare++;
}
R[i] = R[j]; //找到这样的R[j],放入R[i]处
count1++;
while (i < j && R[i].key <= tmp.key)
{
i++; //从左向右扫描,找一个大于tmp.key的R[i]
compare++;
}
R[j] = R[i]; //找到这样的R[i],放入R[j]处
count1++;
}
R[i] = tmp;
disppart(R, s, t);
return i;
}
void QuickSort(RecType R[], int s, int t) //对R[s..t]的元素进行快速排序
{
int i;
if (s < t) //区间内至少存在两个元素的情况
{
i = partition(R, s, t);
QuickSort(R, s, i - 1); //对左区间递归排序
QuickSort(R, i + 1, t); //对右区间递归排序
}
}
void Print()
{
cout << "比较次数:" << compare << "\t移动次数:" << count1 << endl;
}
//双向冒泡
void ShakerSort(RecType R[], int n)
{
int left = 0, right = n - 1, shift = 1;
int i, count=0, compare=0, move=0;
bool change;
while (left < right)
{
change = false;
for (i = left; i < right; i++)
{
compare++;
if (R[i].key > R[i + 1].key)//元素反序
{
move++;
Swap(R[i], R[i + 1]);//将这两个元素交换
change = true;
shift = i;
}
}
cout << " i=" << count<< "\t,结果为:";
DispList(R, n);
count++;
right = shift; //转换方向
for (i = right - 1; i >= left; i--)
{
compare++;
if (R[i].key > R[i + 1].key)//元素反序
{
move++;
Swap(R[i], R[i + 1]);//将这两个元素交换
change = true;
shift = i + 1;
}
}
cout << " i=" << count << "\t,结果为:";
DispList(R, n);
count++;
left = shift;
if (change == false)
{ //本趟没有发生交换,中途结束算法
break;
}
}
cout << "比较次数:" << compare << "\t移动次数:" << move << endl;
}
//二路归并排序( 自底向上的过程 )
int count2 = 0;
int compare1 = 0;
void Merge(RecType R[], int low, int mid, int high)
{
RecType* R1;
int i = low, j = mid + 1, k = 0; //k是R1的下标,i、j分别为第1、2段的下标
R1 = (RecType*)malloc((high - low + 1) * sizeof(RecType)); //动态分配空间
while (i <= mid && j <= high) //在第1段和第2段均未扫描完时循环
{
compare1++;
if (R[i].key <= R[j].key) //将第1段中的记录放入R1中
{
count2++;
R1[k] = R[i];
i++;
k++;
}
else //将第2段中的记录放入R1中
{
count2++;
R1[k] = R[j];
j++;
k++;
}
}
while (i <= mid) //将第1段余下部分复制到R1
{
count2++;
R1[k] = R[i];
i++;
k++;
}
while (j <= high) //将第2段余下部分复制到R1
{
count2++;
R1[k] = R[j];
j++;
k++;
}
for (k = 0, i = low; i <= high; k++, i++) //将R1复制回R中
{
R[i] = R1[k];
}
}
void MergePass(RecType R[], int length, int n) //对整个数序进行一趟归并
{
int i;
for (i = 0; i + 2 * length - 1 < n; i = i + 2 * length) //归并length长的两相邻子表
{
Merge(R, i, i + length - 1, i + 2 * length - 1);
}
if (i + length - 1 < n - 1) //余下两个子表,后者长度小于length
{
Merge(R, i, i + length - 1, n - 1); //归并这两个子表
}
cout << "length=" << length<<":";
DispList(R, n);
}
void MergeSort(RecType R[], int n) //自底向上的二路归并算法
{
int length;
for (length = 1; length < n; length = 2 * length)//进行log2n趟归并
{
MergePass(R, length, n);
}
}
void Print1()
{
cout << "比较次数:" << compare1 << "\t移动次数:" << count2 << endl;
}
//随机生成16个10-99的整数
void Random(int arr[], int n)
{
time_t t; // 定义时间变量
srand((unsigned)time(&t)); //由时间确定随机序列,执行一次
for (int i = 0; i < n; i++)
{
arr[i] = 10 + rand() % 90;
}
}
MySort_main.cpp
#include"MySort.h"
int main()
{
const int n = 16;
int arr[n];
Random(arr,n);
RecType R[n];
CreateList(R, arr, n);
cout << "----直接插入排序----" << endl;
cout << "排序前:" ;
DispList(R, n);
InsertSort(R, n);
cout << "排序后:";
DispList(R, n);
CreateList(R, arr, n);
cout << "----折半插入排序----" << endl;
cout << "排序前:";
DispList(R, n);
BinInsertSort(R, n);
cout << "排序后:";
DispList(R, n);
CreateList(R, arr, n);
cout << "----简单选择排序----" << endl;
cout << "排序前:";
DispList(R, n);
SelectSort(R, n);
cout << "排序后:";
DispList(R, n);
CreateList(R, arr, n);
cout << "----冒泡排序 ----" << endl;
cout << "排序前:";
DispList(R, n);
BubbleSort(R, n);
cout << "排序后:";
DispList(R, n);
CreateList(R, arr, n);
cout << "----快速排序 ----" << endl;
cout << "排序前:";
DispList(R, n);
QuickSort(R, 0, n-1);
cout << "排序后:";
DispList(R, n);
Print();
CreateList(R, arr, n);
cout << "----双向冒泡排序 ----" << endl;
cout << "排序前:";
DispList(R, n);
ShakerSort(R, n);
cout << "排序后:";
DispList(R, n);
CreateList(R, arr, n);
cout << "----二路归并排序 ----" << endl;
cout << "排序前:";
DispList(R, n);
MergeSort(R, n);
cout << "排序后:";
DispList(R, n);
Print1();
}
补充基数排序
#include <iostream>
using namespace std;
#define MAXE 20 //线性表中最多元素个数
#define MAXR 10 //基数的最大取值
typedef struct node
{
int key;
struct node *next;
} NodeType;
int keyi(int s, int i)
{
for(int j=0;j<i;j++)
{
s=s/10;
}
return s%10;
}
void RadixSort(NodeType *&p,int r,int d) //实现基数排序:p为待排序序列链表指针,r为基数,d为关键字位数
{
NodeType *head[MAXR],*tail[MAXR],*t;//定义各链队的首尾指针
int i,j,k;
for (i=0;i<=d-1;i++) //从低位到高位循环
{
for (j=0;j<r;j++) //初始化各链队首、尾指针
head[j]=tail[j]=NULL;
while (p!=NULL) //对于原链表中每个结点循环
{
k=keyi(p->data,i);
if (head[k]==NULL) //进行分配
{
head[k]=p;
tail[k]=p;
}
else
{
tail[k]->next=p;
tail[k]=p;
}
p=p->next; //取下一个待排序的元素
}
p=NULL; //重新用p来收集所有结点
for (j=0;j<r;j++) //对于每一个链队循环
if (head[j]!=NULL) //进行收集
{
if (p==NULL)
{
p=head[j];
t=tail[j];
}
else
{
t->next=head[j];
t=tail[j];
}
}
t->next=NULL; //最后一个结点的next域置NULL
cout<<" 按照"<<i+1<<"位排序:";
DispLink(p);
}
}
三、练习题
1.选择题
(1)对 n 个元素的表做顺序查找时,若查找每个元素的概率相同,则平均查找长度为()。
A.(n-1)/2
B. n/2
C.(n+1)/2
D.n
答案:C
解 释 : 总 查 找 次 数 N=1+2+3+ … +n=n(n+1)/2 , 则 平 均 查 找 长 度 为N/n=(n+1)/2。
(2)适用于折半查找的表的存储方式及元素排列要求为()。
A.链接方式存储,元素无序
B.链接方式存储,元素有序
C.顺序方式存储,元素无序
D.顺序方式存储,元素有序
答案:D
解释:折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
(3)如果要求一个线性表既能较快的查找,又能适应动态变化的要求,最好采用()查找法。
A.顺序查找
B.折半查找
C.分块查找
D.哈希查找
答案:C
解释:分块查找的优点是:在表中插入和删除数据元素时,只要找到该元素对应的块,就可以在该块内进行插入和删除运算。由于块内是无序的,故插入和删除比较容易,无需进行大量移动。如果线性表既要快速查找又经常动态变化,则可采用分块查找。
(4)折半查找有序表(4,6,10,12,20,30,50,70,88,100)。若查找表中元素 58,则它将依次与表中()比较大小,查找结果是失败。
A.20,70,30,50
B.30,88,70,50
C.20,50
D.30,88,50
答案:A
解释:表中共 10 个元素,第一次取[(1+10)/2](向下取)=5,与第五个元素 20 比较,58 大于 20,再取[(6+10)/2](向下取)=8,与第八个元素 70 比较,依次类推再与30、50 比较,最终查找失败。
(5)对 22 个记录的有序表作折半查找,当查找失败时,至少需要比较()次关键字。
A.3
B.4
C.5
D.6
答案:B
解释:22 个记录的有序表,其折半查找的判定树深度为 [log222](向下取)+ 1=5,且该判定树不是满二叉树,即查找失败时至多比较 5 次,至少比较 4 次。
(6)折半搜索与二叉排序树的时间性能()。
A.相同
B.完全不同
C.有时不相同
D.数量级都是 O(log2n)
答案:C
(7)分别以下列序列构造二叉排序树,与用其它三个序列所构造的结果不同的是()。
A.(100,80, 90, 60, 120,110,130)
B.(100,120,110,130,80, 60, 90)
C.(100,60, 80, 90, 120,110,130)
D.(100,80, 60, 90, 120,130,110)
答案:C
解释:A、B、C、D 四个选项构造二叉排序树都以 100 为根,易知 A、B、D 三个序列中第一个比 100 小的关键字为 80,即 100 的左孩子为 80,而 C 选项中 100 的左孩子为 60,故选 C。
(8)在平衡二叉树中插入一个结点后造成了不平衡,设最低的不平衡结点为 A,并已知 A 的左孩子的平衡因子为 0, 右孩子的平衡因子为 1,则应作 ()型调整以使其平衡。
A.LL
B.LR
C.RL
D.RR
答案:C
(9)下列关于 m 阶 B-树的说法错误的是()。
A.根结点至多有 m 棵子树
B.所有叶子都在同一层次上
C.非叶结点至少有 m/2 (m 为偶数)或 m/2+1(m 为奇数)棵子树
D.根结点中的数据是有序的
答案:D
(10)下面关于 B-和 B+树的叙述中,不正确的是()。
A.B-树和 B+树都是平衡的多叉树
B.B-树和 B+树都可用于文件的索引结构
C.B-树和 B+树都能有效地支持顺序检索
D.B-树和 B+树都能有效地支持随机检索
答案:C
(11)m 阶 B-树是一棵()。
A.m 叉排序树
B.m 叉平衡排序树
C.m-1 叉平衡排序树
D.m+1 叉平衡排序树
答案:B
(12)下面关于哈希查找的说法,正确的是()。
A.哈希函数构造的越复杂越好,因为这样随机性好,冲突小
B.除留余数法是所有哈希函数中最好的
C.不存在特别好与坏的哈希函数,要视情况而定
D.哈希表的平均查找长度有时也和记录总数有关
答案:C
(13)下面关于哈希查找的说法,不正确的是()。
A.采用链地址法处理冲突时,查找一个元素的时间是相同的
B.采用链地址法处理冲突时,若插入规定总是在链首,则插入任一个元素的时间是相同的
C.用链地址法处理冲突,不会引起二次聚集现象
D.用链地址法处理冲突,适合表长不确定的情况
答案:A
解释:在同义词构成的单链表中,查找该单链表表中不同元素,所消耗的时间不同。
(14)设哈希表长为 14,哈希函数是 H(key)=key%11,表中已有数据的关键字为 15,38,61,84 共四个,现要将关键字为 49 的元素加到表中,用二次探测法解决冲突,则放入的位置是()。
A.8
B.3
C.5
D.9
答案:D
解释:关键字 15 放入位置 4,关键字 38 放入位置 5,关键字 61 放入位置 6,关键字 84 放入位置 7,再添加关键字 49,计算得到地址为 5,冲突,用二次探测法解决冲突得到新地址为 6,仍冲突,再用用二次探测法解决冲突,得到新地址为 4,仍冲突,再用用二次探测法解决冲突,得到新地址为 9,不冲突,即将关键字 49 放入位置 9。
(15)采用线性探测法处理冲突,可能要探测多个位置,在查找成功的情况下,所探测的这些位置上的关键字 ()。
A.不一定都是同义词
B.一定都是同义词
C.一定都不是同义词
D.都相同
答案:A
解释:所探测的这些关键字可能是在处理其它关键字冲突过程中放入该位置的。
1.选择题
(1)从未排序序列中依次取出元素与已排序序列中的元素进行比较,将其放入已排序序列的正确位置上的方法,这种排序方法称为( )。
A.归并排序
B.冒泡排序
C.插入排序
D.选择排序
答案:C
(2)从未排序序列中挑选元素,并将其依次放入已排序序列(初始时为空)的一端的方法,称为()。
A.归并排序
B.冒泡排序
C.插入排序
D.选择排序
答案:D
(3)对 n 个不同的关键字由小到大进行冒泡排序,在下列()情况下比较的次数最多。
A.从小到大排列好的
B.从大到小排列好的
C.元素无序
D.元素基本有序
答案:B
解释:对关键字进行冒泡排序,关键字逆序时比较次数最多。
(4)对 n 个不同的排序码进行冒泡排序,在元素无序的情况下比较的次数最多为()。
A . n+1
B . n
C . n-1
D.n(n-1)/2
答案:D
解释:比较次数最多时,第一次比较 n-1 次,第二次比较 n-2 次……最后一次比较 1 次,即(n-1)+(n-2)+…+1= n(n-1)/2。
(5)快速排序在下列()情况下最易发挥其长处。
A.被排序的数据中含有多个相同排序码
B.被排序的数据已基本有序
C.被排序的数据完全无序
D.被排序的数据中的最大值和最小值相差悬殊
答案:C
解释:B 选项是快速排序的最坏情况。
(6)对 n 个关键字作快速排序,在最坏情况下,算法的时间复杂度是()。
A . O(n)
B . O(n2)
C . O(nlog2n)
D. O(n3)
答案:B
解释:快速排序的平均时间复杂度为 O(nlog2n),但在最坏情况下,即关键字基本排好序的情况下,时间复杂度为 O(n2)。
(7)若一组记录的排序码为(46, 79,56,38,40,84),则利用快速排序的方法,以第一个记录为基准得到的一次划分结果为()。
A.38,40,46,56,79,84
B.40,38,46,79,56,84
C.40,38,46,56,79,84
D.40,38,46,84,56,79
答案:C
(8)下列关键字序列中,()是堆。
A.16,72,31,23,94,53
B.94,23,31,72,16,53
C.16,53,23,94,31,72
D.16,23,53,31,94,72
答案:D
解释:D 选项为小根堆
(9)堆是一种()排序。
A.插入
B.选择
C.交换
D.归并
答案:B
(10)堆的形状是一棵()。
A.二叉排序树
B.满二叉树
C.完全二叉树
D.平衡二叉树
答案:C
(11)若一组记录的排序码为(46,79,56,38,40,84),则利用堆排序的方法建立的初始堆为()。
A.79,46,56,38,40,84
B.84,79,56,38,40,4684
C.84,79,56,46,40,38
D.84,56,79,40,46,38
答案:B
(12)下述几种排序方法中,要求内存最大的是()。
A.希尔排序
B.快速排序
C.归并排序
D.堆排序
答案:C
解释:堆排序、希尔排序的空间复杂度为 O(1),快速排序的空间复杂度为 O(log2n),归并排序的空间复杂度为 O(n)。
(13)下述几种排序方法中,()是稳定的排序方法。
A.希尔排序
B.快速排序
C.归并排序
D.堆排序
答案:C
解释:不稳定排序有希尔排序、简单选择排序、快速排序、堆排序;稳定排序有直接插入排序、折半插入排序、冒泡排序、归并排序、基数排序。
(14)数据表中有 10000 个元素,如果仅要求求出其中最大的 10 个元素,则采用()算法最节省时间。
A.冒泡排序
B.快速排序
C.简单选择排序
D.堆排序
答案:D
(15)下列排序算法中,()不能保证每趟排序至少能将一个元素放到其最终的位置上。
A.希尔排序
B.快速排序
C.冒泡排序
D.堆排序
答案:A
解释:快速排序的每趟排序能将作为枢轴的元素放到最终位置;冒泡排序的每趟排序能将最大或最小的元素放到最终位置;堆排序的每趟排序能将最大或最小的元素放到最终位置。
2、填空题
1.第i (i = 1, 2, …, n-1) 趟从参加排序的序列中取出第i个元素,把它插入到由第0个~第i-1个元素组成的有序表中适当的位置,此种排序方法叫做________排序。
2.第i (i = 0, 1, …, n-2) 趟从参加排序的序列中第i个~第n-1个元素中挑选出一个最小(大)元素,把它交换到第i个位置,此种排序方法叫做________排序。
3.每次直接或通过基准元素间接比较两个元素,若出现逆序排列,就交换它们的位置,这种排序方法叫做________排序。
4.每次使两个相邻的有序表合并成一个有序表,这种排序方法叫做________排序。
5.在直接选择排序中,排序码比较次数的时间复杂度为O(________)。
6.在直接选择排序中,数据对象移动次数的时间复杂度为O(________)。
7.在堆排序中,对n个对象建立初始堆需要调用________次调整算法。
8.在堆排序中,如果n个对象的初始堆已经建好,那么到排序结束,还需要从堆顶结点出发调用________次调整算法。
9.在堆排序中,对任一个分支结点进行调整运算的时间复杂度为O(________)。
10.对n个数据对象进行堆排序,总的时间复杂度为O(________)。
11.给定一组数据对象的排序码为 { 46, 79, 56, 38, 40, 84 },则利用堆排序方法建立的初始堆(最大堆)为________。
12.快速排序在平均情况下的时间复杂度为O(________)。
13.快速排序在最坏情况下的时间复杂度为O(________)。
14.快速排序在平均情况下的空间复杂度为O(________)。
15.快速排序在最坏情况下的空间复杂度为O(________)。
16.给定一组数据对象的排序码为 {46, 79, 56, 38, 40, 84},对其进行一趟快速排序,结果为________。
17.在n个数据对象的二路归并排序中,每趟归并的时间复杂度为O(________)。
18.在n个数据对象的二路归并排序中,整个归并的时间复杂度为O(________)。
参考答案:
-
插入 2. 直接选择 3. 交换
-
两路归并 5. n2 6. n
-
n/2 8. n-1 9. log2n
-
nlog2n 11. 84, 79, 56, 38, 40, 46 12. nlog2n
-
n2 14. log2n 15. n
-
[40 38] 46 [79 56 84] 17. n 18. nlog2n
19.给出一个施加于链表的选择排序的算法。算法中用到一个临时的表头结点 head,作为结果链表的表头结点,每次从first链上摘下值最大的结点current链入 head之后。算法结束前,将 head删除。
template <class T>
void LinkList<T> :: ListSelectSort ( ) {
LinkNode<T> * head = new ListNode<T>, * current, * pre, * p, * q;
int i = 0;
while (______①______) {
p = current = first; q = NULL;
while ( p != NULL ) {
if ( p->data >______ ② ______)
{ pre = q; current = p; }
q = p; p = p->link;
}
if (current == first ) ______③______;
else pre->link = current->link;
if ( !i ) last = current;
i++;
current->link = head->link; ______ ④______;
}
first = head->link; delete head;
}
①first != NULL
②current->data
③first = first->link
④head->link = current
3、判断题
1.直接选择排序是一种稳定的排序方法。
2.若将一批杂乱无章的数据按堆结构组织起来, 则堆中各数据是否必然按自小到大的顺序排列起来。
3.当输入序列已经有序时,起泡排序需要的排序码比较次数比快速排序要少。
4.在任何情况下,快速排序需要进行的排序码比较的次数都是O(nlog2n)。
5.在2048 个互不相同的排序码中选择最小的5个排序码,用堆排序比用锦标赛排序更快。
6.若用m个初始归并段参加k路平衡归并排序,则归并趟数应为 【log2m】(向上取)。
7.堆排序是一种稳定的排序算法。
8.对于某些输入序列,起泡排序算法可以通过线性次数的排序码比较且无需移动数据对象就可以完成排序。
9.如果输入序列已经排好序,则快速排序算法无需移动任何数据对象就可以完成排序。
10.希尔排序的最后一趟就是起泡排序。
11.任何基于排序码比较的算法,对n个数据对象进行排序时,最坏情况下的时间复杂度不会低于O(nlog2n)。
12.不存在这样一个基于排序码比较的算法:它只通过不超过9次排序码的比较,就可以对任何6个排序码互异的数据对象实现排序。
参考答案: 1. 否 2. 否 3. 是 4. 否 5. 否
- 否 7. 否 8. 是 9. 否 10. 是
- 是 12. 是