文章目录
前言
按照大纲顺序整理
一、预备知识
a. 了解C++和Java基本语法结构,掌握递归思想。
二、程序性能
a. 了解复杂性表示和计算方法
- 【简答题】给出一段极短的程序,大概三四行,包括for和while语句,计算复杂度。一般和求和和阶乘相关。
b. 掌握插入排序、选择排序、冒泡排序、名次排序的基本思想
- 【算法题】在算法题中涉及到冒泡排序的算法思想。
/**给定一个整数数组,设计一个算法,将数组中的所有0元素全部移到数组末尾,保持其他非零元素相对位置不变,要求除数组本身外,不使用其他辅助存储空间*/
void zeroBubble(int a[], int n)
{
bool flag = false;
for(int i = 0; i < n-1; i++)
for(int j = 0; j < n-1-i; j++)
{
if(a[j] == 0 && a[j+1] != 0)
{
a[j] = a[j+1];
a[j+1] = 0;
flag = true;
}
}
if(flag == false)
return;
}
- 【简答题】罗列出基本排序算法的最好最坏和平均情况下的时间复杂度以及其它性质
- 【简答题】根据排序算法写出第一趟或前几趟排序的结果
/*对关键字序列{23,16,72,60,25,9,68,71,52}进行堆排序,写出输出两个关键字后的剩余堆*/
/*有6个元素{20,15,12,7,9,18},进行直接插入递增排序,写出第三趟排序的结果*/
/*对关键字序列{10,7,18,31,15,9,22,26},使用冒泡排序/快速排序(选第一个记录为支点),基础排序,写出每趟排序后的结果*/
- 【简答题】简述快速排序、最大堆排序、基数排序等各排序算法的思想
/*快速排序:首先选取一个枢轴(一般选第一个元素),指针i和指针j分别在序列的开头和结尾,取出枢轴位置上的树。指针j从后往前移动,遇到比枢轴小的数停住,和指针i交换。指针i从前往后扫,遇到比枢大的数停住,和指针j交换,重复以上过程,直到下一次两指针停住时,指针j和指针i相遇,交换枢轴元素和指针j指向的元素。至此,枢轴左边的数都小于等于它,右边的数都大于或等于它。然后对枢轴两边的数分别进行快速排序。*/
/*最大堆排序:先将要排序的n个元素初始化为一个最大堆,然后每次输出堆顶元素,再将堆底最后一个元素移到堆顶,对堆顶元素进行向下冒泡使其继续保持最大堆性质,重复上述操作,在最终的序列中各元素将按非递减次序排列。堆初始化的时间为O(n),n-1次删除操作的时间复杂度为O(nlogn),因此总时间为O(nlogn)*/
- 【简答题】有5000个无序元素,公式化描述(数组),要求最快速度选取最大的10个元素。请问,快速排序、堆排序、基数排序、归并排序四种方法,采用哪种方法最好,为什么?
/*采用堆排序的方法最好,在时间复杂度上,平均时间复杂度基数排序为O(n*k),其余三种算法为O(nlogn)。在空间复杂度上,快速排序为O(logn),堆排序为O(1),基数排序为O(n+k),归并排序为O(n)。且堆排序不需要等到排序算法结束就能选出前10个最大值,初始化堆完成后,删除十次最大堆堆顶元素即可*/
三、数据描述
a. 掌握线性表的公式化描述、链表描述、间接寻址等存储方法
- 【简答题】顺序表和链表的性质和优劣
顺序表:
定义:线性表的顺序存储称为顺序表。
性质:逻辑上相邻的两个元素在物理位置上也相邻,具有随机访问特性。
优点:可以顺序存取,也可以随机存取,存取时间复杂度为O(1)
缺点:需要预先分配足够大的空间,插入和删除平均需要移动一半的元素,插入删除时间复杂度为O(n), 不适用于需要频繁插入和删除的应用。
适用对象:经常按序号访问数据元素的对象,且长度和存储规模可以估计。
链表:
定义:线性表的链式存储称为链表
性质:不适用于连续的存储单元,逻辑上相邻的两个元素在物理位置上不一定相邻。
优点:适合进行插入删除操作,不需要提前分配足够的存储空间,删除和插入操作需要的时间复杂度为O(1)。
缺点:不支持随机存取,元素因指针需要额外占用存储空间,查找元素的时间复杂度为O(n)。
适用对象:线性表的长度和规模不容易估计的情况,频繁进行插入删除操作的线性表。
间接寻址:
优点:间接寻址中,数组不在保存节点,而是保存节点的地址,可以看做是公式化描述和链式描述的结合体。存取时间复杂度为O(1),插入删除操作仅仅修改节点的地址在数组中的位置,优于使用链表描述的插入删除操作。
缺陷:保存节点的地址增加了额外的开销
- 【顺序表】设一组有序的记录关键字序列为(13,18,24,35,47,50,62,83,90),查找方法用折半查找,要求计算出差找关键字62时的比较次数并计算出查找成功时的平均查找长度
比较次数:从n/2向下取整开始算就好。
平均查找长度:对集合中的每一个元素计算查找次数最后除以元素总数。
- 【间接寻址】一个班级有15个学生,使用1,2,3,…,14,15作为学号。(i,j)表示学生i和学生j参加了同一个兴趣小组。对给出的集合S={(1,2),(6,9),(15,7),(1,6),(10,8),(8,11)},请基于模拟指针设计数据结构表示集合S中的兴趣小组,并罗列S中所有的兴趣小组。
data: 1 2 6 7 8 9 10 11 15
link: -4 1 1 -2 -3 1 8 8 7
data为学号,link为元素的根节点的索引(根节点的索引为负数)
- 【公式描述】假定采用下述公式来描述一个线性表
l o c a t i o n ( i ) = ( l o c a t i o n ( 1 ) + i − 1 ) location(i) = (location(1)+i-1) % MaxSize; location(i)=(location(1)+i−1)
与专门保留一个表长的做法不同的是,用变量first和last来指出表的第一个和最后一个元素的位置。试描述如何删除元素,并分析操作的时间复杂度。
插入:假如在线性表第i个位置插入新元素,将i带入公式,得到插入位置location(i)。若该位置不满足first<=location(i)<=last, 插入失败。否则,若该位置为空,直接插入该元素。若该位置不为空,将线性表第i个元素以及之后的所有的元素后移一个位置,空出一个位置插入新元素,时间复杂度为O(n)。
删除:假如在线性表第i个位置删除新元素,将i带入公式,得到插入位置location(i)。若该位置不满足first<=location(i)<=last, 删除失败。否则,将线性表第i+1个元素以及之后的所有的元素左移一个位置,该线性表的长度减一,时间复杂度为O(n)。
- 【循环单链表】猴子围圈投票问题,有n个猴子,按顺时针编号,分别是1到n。现在开始选大王,从第一个开始顺时针报数,报到m的猴子出圈,紧接着下一个开始从1顺时针报数,如此下去,最后剩下来的就是大王,问使用什么数据结构可以选出大王。
约瑟夫问题,使用循环单链表可解。
循环单链表:是单链表,表尾节点*r的next域指向链表头结点LinkList,故表中没有指针域为NULL的结点。
b. 了解遍历器的作用和实现方法
在图中可能会用到,其他地方还没考过
c. 掌握线性表的插入、删除、合并等运算方法
- 【算法题】【顺序表】判断以@结尾的字符串是否为回文
算法思想:从左往右和从右往左同时遍历,如果有不一样的,则不是回文。
bool isHui(char a[])
{
int i = 0, len = 0;
bool res = true;
while(a[i]!='@')
len++;
for(int j = 0; j < len/2; j++)
if(a[j] != a[len-j-1])
res = false;
return res;
}
- 【算法题】【链表】线性表用单链表存储,请设计单链表类Chain的一成员函数simpleSelectSortList(),实现简单选择排序算法,并分析算法复杂度
template <class T>
struct ChainNode
{
T data;
ChainNode<T>* next;
};
template <class T>
class LinearList{
public:
LinearList(ChainNode<T>* root){this->root = root;}
~LinearList();
SimpleSelectSortList();
private:
ChainNode<T>* root;
};
template <class T>
void LinearList<T> :: SimpleSelectSortList() // 假设链表含有头节点
{
if(root == NULL || root->next == NULL)
return;
ChainNode<T>* p = root->next;
ChainNode<T>* q;
ChainNode<T>* minN;
while(p != NULL)
{
minN = p;
q = p->next;
while(q != NULL)
{
if(q->data < minN->data)
minN = q;
q = q->next;
}
if(minN->data < p->data)
{
// swap
T tem = p->data;
p->data = minN->data;
minN->data = tem;
}
p = p->next;
}
}
- 【算法题】【顺序表】设计算法实现将数组r[s:t] 中所有奇数移到所有偶数之前,要求算法复杂度为O(n),n为数组元素的个数。
算法思想:快速排序,一个从左往右,一个从右往左,如果左边遇到偶数,右边遇到奇数,停下,交换,直到两指针交叉。
void quickSort(int r[], int s, int t)
{
int p1 = s;
int p2 = t;
while(p1 < p2)
{
while(r[p1]%2 == 1 && p1<=t)
p1++;
while(r[p2]%2 == 0 && p2>=s)
p2--;
if(p1 < p2)
{
int tem = r[p1];
r[p1] = r[p2];
r[p2] = tem;
}
}
}
- 【算法题】【链表】有一个单链表L,其结点的元素值以非递减有序排列,请编写函数purge,删除单链表中多余的元素值相同的结点。
算法思想:因为是非递减,如果遇见相邻的一样就删掉
template <class T>
struct ChainNode
{
T data;
ChainNode* next;
};
void purge(ChainNode* root)// 假设链表含有头节点
{
if(root == NULL || root->next == NULL)
return;
ChainNode* pre = root->next;
ChainNode* p = pre->next;
while(p!= NULL)
{
if(pre->data == p->data)
{
pre->next = pre->next->next;
delete p;
p = pre->next;
}else
{
pre = p;
p = p->next;
}
}
}
- 线性表使用公式化描述方式存储。编写一个函数,从给定的线性表A中删除值在x到y(包括x和y)之间的所有元素,要求以较高的效率来实现。
算法思想:把在删除范围之内的元素标记上,遇到后面不在范围之内的,之间往前移动,因为被标记的元素可视为空,最后长度-k,确保所有在范围内的元素都被清空。
template <class T>
struct LinearList
{
T* a;
int length;
};
template <class T>
void deleteXY(LinearList<T> &L, T x, T y)
{
int k = 0;
for(int i = 0; i < L.length; i++)
{
if(L.a[i] >= x && L.a[i] <= y)
k++;
else
L.a[i-k] = L.a[i];
}
L.length -= k;
}
- 为带表头的单链表Chain编写一个成员函数Reverse,该函数对链表进行逆序操作,要求逆序操作原地就地进行,不分配任何新的结点。要求首先给出类声明,在类的声明中,其它成员函数可省略。
算法思想:指针反过来,最后把头结点接到尾巴上。
// 第一题
template <class T>
struct ChainNode
{
ChainNode<T>* next;
T data;
};
template<class T>
class Chain
{
public:
Chain()
{
first = NULL;
};
~Chain();
private:
ChainNode<T>* first;
};
template <class T>
void Reverse(ChainNode* head)
{
if(head == NULL || head->next == NULL) // 假设链表含有头结点
return;
ChainNode<T>* pre = head->next;
ChainNode<T>* p = pre->next;
ChainNode<T>* tem;
pre->next = NULL;
while(p != NULL)
{
tem = p->next;
p->next = pre;
pre = p;
p = tem;
}
head->next = pre;
}
d. 掌握箱子排序,基数排序
- 【潜在考点】箱子排序
- 【基数排序】基数排序已经在上面提到过
四、数组和矩阵
a. 掌握对角矩阵、三对角矩阵、对称矩阵等特殊矩阵的特征,掌握存储方法和基本运算实现
20.【对称矩阵】 对一个n阶方阵,如果是一个对称矩阵,即矩阵元素 a i j = a j i a_{ij} = a_{ji} aij=aji,为了节省时间,我们使用一维数组元素D[]来存储下三角元素,D[]元素个数为多少个?假设我们采用行主映射的方式,那么任意矩阵元素 a i j a_{ij} aij在数组中的位置是什么?
元素个数:为等差数列,n*(n+1)/2
数组中的位置除非特别提及,其位置从a[0]开始。
确定元素的点(i, j),除非特别提及,是从(1,1)开始。
以下三角为例,位置k = i*(i-1)/2 + j -1
- 【特殊矩阵】矩阵T满足对于所有的i和j,有T(i, j) = T(i-1, j-1), 其中i>1, j<=n。该矩阵最多有多少个不同的元素?仅将这些不同元素映射存储在一维数组a中以减少冗余,给出元素到数组a[k]的映射方案
元素个数:共有2*n-1个元素
映射:k = i-j+n-1
- 【三对角矩阵】将三对角矩阵A中三条对角线的元素按行存放在一维数组B中,B中共有多少元素,给出在对角线上的元素在B中的映射公式
元素个数:3*n-2
映射公式:2*i+j-3 至于如何推导的分为两部分,在我另一篇草稿里有写
- 【五对角矩阵】将五对角矩阵A中五条对角线的元素按行存放在一维数组B中,B中共有多少元素,给出在对角线上的元素在B中的映射公式
元素个数:5*n-6
映射公式:4*i+j-5
五、堆栈
a. 掌握堆栈的基本概念、基本操作和实现方法
-
堆栈的应用会隐藏在算法里,比如非递归的二叉树前序、中序和后序遍历和DFS算法。
-
设有编号为123456的六辆车,顺序进入一个栈式结构的站台。能否得到435612和135426的出站序列,请说明理由
注意一定是从左往右读,那么第一辆车就是左边的第一个数。
可以通过在草稿上,写写画画来判断。
在这里,435612不可行,因为2必定比1先出站。135426可行
- 设栈S的初始状态为空,现有5个元素组成的序列{a,b,c,d,e},对该序列在S栈上依次进行如下操作(从a开始,出栈之后不再进栈):进、进、进、出、进、出、进、出、出、出,请回答出栈序列
出栈序列:c、d、e、b、a
- 设有编号A,B,C,D的四辆车,顺序进入一个栈式结构的站台,试写出这四辆车开出车站的所有不可能的顺序
这个题比较考验概念理解,使用枚举法比如A先出栈、B先出栈、C先出栈、D先出栈,共十种
b. 掌握括号匹配、离线等价类的实现思想
- 【潜在考点】括号匹配
- 【潜在考点】离线等价类
六、队列
a. 掌握队列的基本概念,基本操作和实现方法
-
堆栈的应用会隐藏在算法里,比如非递归的二叉树层次遍历和BFS算法。
-
描述栈与队列的相同点与不同点
-
简述顺序存储队列的满和空的条件
相同点:都是操作受限的线性表,都只能在线性表的两端或一端进行操作,都具有顺序和链式两种存储结构。
不同点:栈是只能在一端进行插入或删除操作,其中允许进行插入和删除的一端称为栈顶,它是动态变化的,另一端称为栈底,它是固定不变的,特点为先进后出。队列是允许在表的一端进行插入,在另一端进行删除,主要特点为先进先出。
方法:牺牲一个队列单元来区分队空和队满。
队空条件:Q.front = Q.back;
队满条件:Q.front = (Q.back+1)%maxSize;
七、跳表和散列
a. 了解跳表的基本概念,基本操作和实现方法
- 【潜在考点】跳表
b. 掌握散列的基本概念,基本操作和实现方法
- 散列表长度为11,散列函数为Hash(k) = k%11,元素序列为{1, 3, 19, 8, 14, 25, 6, 28}。请回答下列问题:
(1)请画出该序列的线性开型寻址散列存储结构
(2)请画出该序列的链表散列存储结构
(3)什么是散列表的负载因子(表示桶的装满程度)
(4)在线性开型寻址散列表实现删除时,如果只是把删除元素所在的桶置空,会出现什么问题
(5)对于第4问,请写出一种解决办法
(6)搜索元素19和14所需要的比较次数是多少
(7)给出删除元素8后的散列表结构
(8)在等概率情况下,查找成功的平均次数(有元素的桶)
(9)在等概率情况下,查找失败的平均次数(所有的桶)
(10) 什么是哈希表?
(11) 冲突可能与哪些因素有关?
哈希表:根据关键字而直接进行访问的数据结构
冲突产生的原因:散列函数可能会把两个或者多个不同的关键字映射到同一地址;散列函数设计不合理;散列表长度不够
八、二叉树
a. 掌握二叉树的基本概念、存储方法、常用操作和特征
- 前序、中序、后序和层次遍历,画出树或者互相写序列的题目
1 已知二叉树的前序和后序序列为ABC和CBA,画出四棵不同的二叉树
2 根据二叉树的数组存储,画出树的各种遍历序列
3 前序序列A,B,C,D的二叉树,中序序列可能是D,A,B,C吗
4 二叉树的层次序列为ABCDEFGHIJ,中序遍历序列为DBGEHJACIF,写出该二叉树的前序遍历序列
5 二叉树的前序遍历序列为ABDEGHJCFI,中序遍历序列为DBGEHJACIF,写出该二叉树的层次遍历序列
6 二叉树的前序遍历序列为ABDEGHJCFI,中序遍历序列为DBGEHJACIF,写出该二叉树的后序遍历序列
7 二叉树的前序遍历序列为ABDEGHJCFI,中序遍历序列为DBGEHJACIF,写出该二叉树的叶节点
- 和二叉树的度,结点个数相关的问题
核心公式:树中结点数 = 总分叉数 + 1 = (从1到n求和)度为k的结点的个数*k + 1 = 度为0的结点个数(叶子结点) + 度为1的结点个数 + ...
1 设树T的度为4,其中度为1,2,3,4的节点个数分别为4,2,1,1,则T中的叶子数为多少
2 假设高度为h的二叉树只有度为0和度为2的结点,则此类二叉树所包含的结点数至少为多少,请给出推导过程
b. 掌握二叉树的前序、中序、后台、按层次遍历方式
- 【算法题】最小深度是指根结点到最近的叶子结点的最短路径上的结点的个数,给定一个链式存储的二叉树,设计算法求其最小深度,叙述算法思想并分析算法的时间复杂度。
template <class T>
struct BTreeNode
{
T data;
BTreeNode<T>* leftChild;
BTreeNode<T>* rightChild;
};
// 递归
template <class T>
int minDepth(BTreeNode<T>* t)
{
int res;
if(t == NULL)
return 0;
if(t->leftChild == NULL && t->rightChild== NULL)
{
res = 1;
}
if(t->leftChild != NULL && t->rightChild != NULL)
{
int ld = minDepth(t->leftChild);
int rd = minDepth(t->rightChild);
res = (ld > rd ? rd : ld) + 1;
}
if(t->leftChild == NULL && t->rightChild != NULL)
{
int rd = minDepth(t->rightChild);
res = rd + 1;
}
if(t->leftChild != NULL && t->rightChild == NULL)
{
int ld = minDepth(t->leftChild);
res = ld + 1;
}
return res;
}
- 判断一棵树是否为最大树
//第二题
template <class T>
struct BTreeNode
{
T data;
BTreeNode<T>* leftChild;
BTreeNode<T>* rightChild;
};
template <class T>
bool isMaxTree(BTreeNode<T>* root)
{
BTreeNode<T>* p = root;
bool res1, res2;
if(p == NULL)
return true;
if(root->leftChild != NULL && root->leftChild->data > p->data)
return false;
if(root->rightChild != NULL && root->rightChild->data > p->data)
return false;
res1 = isMaxTree(p->leftChild);
res2 = isMaxTree(p->rightChild);
return res1 && res2;
}
- 试编写算法,求给定二叉树上从根节点到叶子节点的一条路径长度等于树的深度减一的路径,若这样的路径存在多条,则输出路径终点在“最左”的一条
// 第二题
template <class T>
struct BTreeNode
{
T data;
BTreeNode<T>* leftChild;
BTreeNode<T>* rightChild;
};
template <class T>
int getDepth(BTreeNode<T>* root)
{
int lh, rh;
if(root == NULL)
return 0;
lh = getDepth(root->leftChild);
rh = getDepth(root->rightChild);
return (lh > rh ? lh : rh) + 1;
}
template <class T>
void findPath(BTreeNode<T>* root, int n)
{
int h = getDepth(root);
stack<BTreeNode<T>*> s;
int length = 1;
T* Path = new T[n];
BTreeNode<T>* p = root;
while(p != NULL || !s.empty())
{
if(p != NULL)
{
path[length++] = p;
s.push(p);
p = p->leftChild;
}
else
{
p = s.top();
s.pop();
if(p->rightChild == NULL) //到叶节点了,判定是否为所求路径
{
if(length == h)//因为有length++,该路径的实际长度为length-1
{
for(int i = 1; i< length; i++)
{
cout << path[i]<<endl;
}
}
}
p = p->rightChild;
}
return;
}
- 编写算法,求解从根节点到给定点p所指节点之间路径
template <class T>
struct BTreeNode
{
T data;
BTreeNode<T>* leftChild;
BTreeNode<T>* rightChild;
};
template <class T>
T* findPath(BTreeNode* root, BTreeNode* p)
{
if(p == NULL)
throw "p can not be empty.";
int length = 0;
int n = numberOfVertices();
T* path = new T[n];
rFindPath(root, p, path, length);
return path;
}
template <class T>
bool rFindPath(BTreeNode* root, BTreeNode* p, T* path, int length)
{
bool lres, rres;
if(root == NULL)
return false;
path[length++] = root;
if(root == p)
return true;
// 这是一颗树,所以只有一条路径,左子树和右子树最多一个返回true。
lres = rFindPath(root->leftChild, p, path, length);
if(lres == false)
{
rres = rFindPath(root->rightChild, p, path, length);
}
if(lres || rres)
return true;
length--;
return false;
}
}
- 计算二叉树中度为1的结点个数
// 第二题
template <class T>
struct BTreeNode
{
T data;
BTreeNode* leftChild;
BTreeNode* rightChild;
};
int getDegree(BTreeNode* root)
{
int num = 0;
if(root == NULL)
return 0;
if(root->leftChild != NULL && root->rightChild == NULL)
{
num++;
}
if(root->rightChild != NULL && root->leftChild == NULL)
{
num++;
}
int lres = getDegree(root->leftChild);
int rres = getDegree(root->rightChild);
return (num+lres+rres);
}
- 编写算法,删除二叉搜索树的最小元素。
// 第二题
template <class T>
struct BTreeNode
{
T data;
BTreeNode<T>* leftChild;
BTreeNode<T>* rightChild;
};
template <class T>
void deleteMin(BTreeNode<T>* root)
{
if(root == NULL)
return;
BTreeNode<T>* pre;
BTreeNode<T>* p = root;
while(p->leftChild != NULL)
{
pre = p;
p = p->leftChild;
}
if(pre == NULL)
{
root = p->rightChild;
delete p;
}else
{
pre->leftChild = p->rightChild;
delete p;
}
}
- 判断一棵树是否为满二叉树
// 第二题
template <class T>
struct BTreeNode
{
T data;
BTreeNode* leftChild;
BTreeNode* rightChild;
};
template <class T>
int getDepth(BTreeNode<T>* root)
{
int lh, rh;
if(root == NULL)
return 0;
lh = getDepth(root->leftChild);
rh = getDepth(root->rightChild);
return (lh > rh ? lh : rh) + 1;
}
template <class T>
int getNum(BTreeNode<T>* root)
{
if(root == NULL)
return 0;
int ln = getNum(root->leftChild);
int rn = getNum(root->rightChild);
return (ln + rn + 1);
}
template <class T>
bool isFull(BTreeNode<T>* root);
{
int h = getDepth(root);
int num = getNum(root);
if((math.pow(2, h)-1) == num)
return true;
return false;
}
- 编写算法,求出二叉树中结点的度数为1的个数,并以n返回(要求不能使用递归)
template <class T>
struct BTreeNode
{
T data;
BTreeNode<T>* leftChild;
BTreeNode<T>* rightChild;
};
template<class T>
int getDegree(BTreeNode* root)
{
BTreeNode<T>* p = root;
stack<BTreeNode<T>*> s;
int num = 0;
if(p == NULL)
return -1;
while(p != NULL || !s.empty())
{
if(p != NULL)
{
if(p->leftChild == NULL && p->rightChild != NULL)
num++;
if(p->leftChild == NULL && p->rightChild != NULL)
num++;
s.push(p);
p = p->leftChild;
}
else
{
p = s.top();
s.pop();
p = p->rightChild;
}
}
return num;
}
c. 掌握基于树存储的在线等价类实现
- 【潜在考点】基于数存储的在线等价类实现
d. 了解树的存储方法
- 根据树的数组描述或链表描述,画树或判断其节点位置、存储优缺点
二叉树还可以用数组进行逻辑描述,根据映射公式来确定节点之间的关系。
优点:可以随机存取,不需要指针,空间利用率高。适用于完全二叉树和满二叉树。
缺点:一个n个元素的二叉树最多需要2^n个空间来存储,当二叉树缺少的元素很多时,会造成空间的极大浪费。
九、优先队列
a. 掌握堆的基本概念和插入、删除和初始化方法
b. 掌握堆排序的思想(经常与a一块考)
- 对关键字序列{23,16,72,60,25,9,68,71,52}进行堆排序,输出两个关键字后的剩余堆是什么?
堆排序与其它排序有一点不同,就是分步骤的。首先是堆的初始化,使用的是筛选法建堆,其时间复杂度为O(n)。
之后输出关键字便是最大(最小)堆的删除
插入是向上冒泡
c. 掌握霍夫曼树、霍夫曼编码的实现方法
- 有一份电文中共使用5个字符:a,b,c,d,e,它们出现的频率依次是 {1,3,19,8,14,25,6,28}
1)试构造关于w的一颗霍夫曼树
2)求其加权路径长度WPL
3)求出每个字符的huffman编码
d. 了解左高树基本概念和插入、删除、合并和初始化方法的实现
- 什么是优先队列?什么是堆?为什么使用堆描述优先队列比使用线性逻辑描述优先队列更好?什么情况下使用左高树描述优先队列比使用堆描述优先队列好?
堆:堆是一颗有最大堆和最小堆之分 / 在最大堆中每个节点的值都大于等于其子节点(如果有子节点的话)的值 / 最小堆定义类似 / 的完全二叉树。
优先队列:优先级队列是一种数据结构,每次从优先级队列中取出的是具有最高优先级的元素。
为什么使用堆:若使用线性逻辑描述优先级队列,插入删除操作的平均时间复杂度为O(n),使用堆,则插入删除的平均时间复杂度为O(logn),所以使用堆描述优先级队列。
左高树:当两个优先级队列或多个长度不同的队列需要合并时,需要使用左高树,左高树可在对数时间内实现两个优先级队列的合并。
十、搜索树
a. 掌握二叉搜索树(排序树)基本概念和插入、搜索、删除的实现方法
- 一个二叉搜索树,设任一条从根到叶子的路径包含的节点集合为S2,这条路径中所有左边的点的集合为S1,右边所有店的集合为S3,设a,b,c分别为S1,S2,S3中的任意元素,是否有a<b<c,为什么?
b. 掌握二叉平衡树(AVL树)基本概念和插入、搜索、删除的实现方法
- 给出按关键字序列{19,36,88,12,16,77,60}生成的二叉搜索树和AVL搜索树
- 推导12个元素构成的二叉平衡树(AVL)的最大深度,并画出示例
- 为什么仅调整最小不平衡树就不存在其他不平衡点?
- 什么是AVL树?高度为h的AVL树最少有多少结点,最多有多少结点,n个节点的AVL树的高度是多少?
AVL参照笔记
二叉平衡树最大深度元素个数递推公式:N_(h) = N_(h-1)+N_(h-2)+1,N_(0) = 0,N_(1) = 1,N_(2) = 2
因为AVL树不平衡后,会使最小不平衡树高度加1,正是因为最小不平衡树高度加1导致的不平衡,经过调整后,最小不平衡树的高度回复了原来的值,所以树的其它部分的平衡因子又与之前的相同,因此不再会有平衡点
- 【潜在考点】二叉搜索树相对于跳表和散列的优势
c. 掌握m叉搜索树和B树基本概念以及插入、搜索、删除的实现方法
- 依次输入{1,2,6,7,11,4,8,13,10,5,17,9,16,20,15}建立5阶B树,写出建立的过程,然后写出删除结点8和结点16后的过程,计算磁盘的读写次数(等待看课本)
- 一个8key值的3阶B树最多有多少节点?最少有多少?并画图表示
- 【潜在考点】m叉搜索树
看笔记
m阶B树
B-树首先是一棵m叉搜索树,并且是一颗扩展树,并且满足以下条件
a. 根节点至少有两个孩子
b. 除根节点以外,所有内部节点至少存在孩子数目为(m/2)的上界
c. 第二条也就是说所有内部节点至少存在的元素数目为孩子数目 - 1。
d. 所有外部节点在同一层。
e. 只包含一个根节点的树也是B树。
十一、图
a. 掌握图的基本概念
- 【内含考点】该考点内容包含在题中之中,可参考笔记复习
- 【证明题】对有向图来说,该“该图为无环图”等价于“该图的顶点存在这样的编号,使其邻接矩阵为下三角形且对角线为全0”
充分性:由于不存在从某顶点出发再回到自身的边,所以邻接矩阵主对角元素均为0;由于不存在环,至少可以找到一入度为0的顶点,其编号最小,邻接顶点按BFS顺序从小到大编号,其邻接矩阵为下三角形。
必要性:反证法:若图含有环,那么对于邻接矩阵必存在至少一个(i,j),i<=j,使(j,i)不为零,与条件不符,得证。
- 【证明题】若无向图G的顶点度数的最小值大于或等于2,证明G必然存在环路
反证法:若无向图G的顶点度数的最小值大于等于2,那么G必然不存在环路。若G不存在环路,至少有一点为端点,端点处的度数为1,与条件矛盾,所以G必然存在环路。
b. 掌握图的邻接矩阵和邻接链表存储方法
c. 掌握图的深度优先和广度优先遍历算法
- 由图的邻接表或邻接矩阵,求
(1)从顶点1开始的深度优先遍历所得的序列
(2)从顶点1开始的深度优先遍历所得的生成树
(3)从顶点1开始的广度优先遍历所得的序列
(4)从顶点1开始的广度优先遍历所得的生成树
(5)写出该图的一个拓扑序列
(6)由邻接表画邻接矩阵,由邻接矩阵画邻接表
(7)寻找某点到某点都所有简单路径
(8)列出图中所有的有向环路和它们的长度
d. 掌握图的寻找路径和寻找连通构件的方法
- 【算法题】设计算法,判断图的邻接表中是否存在V_i到V_j的路径
/**
用C++或java语言,判断图的邻接表中是否存在Vi到Vj的路径(i != j)
*/
// 边表
struct ChainNode
{
int data;
ChainNode* next;
};
// 顶点表
struct GraphChain
{
int data;
ChainNode* firstNode;
};
struct linkedDigraph // 邻接表
{
ChainNode* aList;
int n; //顶点数
int e; //边数
};
bool findPath(int source, int goal, linkedDigraph &G)
{
int n = numberOfVertices();
int* reach = new int[n+1];
for(int i = 0; i<n+1; i++)
{
reach[i] = 0;
}
if(source == goal || rFindPath(source,goal,G,reach))
{
delete [] reach;
return true;
}
delete [] reach;
return false;
}
bool rFindPath(int source, int goal, linkedDigraph &G,reach)
{
reach[source] = 1;
ChainNode* u = G.aList[source]->firstNode;
while(u != NULL)
{
if(reach[u->data] == 0)
{
if(u->data == goal || rFindPath(u->data,goal,G,reach))
return true;
}
u = u->next;
}
return false;
}
- 【算法题】已知有向图以邻近表表示,编写算法,求指定顶点k的入度
// 边表
struct ChainNode
{
int data;
ChainNode* next;
};
// 顶点表
struct GraphChain
{
int data;
ChainNode* firstNode;
};
struct linkedDigraph // 邻接表
{
ChainNode* aList;
int n; //顶点数
int e; //边数
};
int inDegree(int k, linkedDigraph &G)
{
int n = numberOfVertices();
int in = 0;
for(int i = 1; i<=n; i++)
{
ChainNode* u = G.aList[i]->firstNode;
while(u->next != NULL)
if(u->data == k)
in++;
u = u->next;
}
return in;
}
- 【算法题】已知无向图以邻接表存储,试编写算法删除边(i,j)
// 第三题
// 边表
struct ChainNode
{
int data;
ChainNode* next;
};
// 顶点表
struct GraphChain
{
int data;
ChainNode* firstNode;
};
struct linkedDigraph // 邻接表
{
ChainNode* aList;
int n; //顶点数
int e; //边数
};
void deleteIJ(int i, int j, linkedDigraph &G)
{
ChainNode* p = G.aList[i]->firstNode;
ChainNode* pre = p;
// 正好是头结点
if(p->data == j)
{
aList[i]->firstNode = p->next;
delete p;
return;
}
// 不是头结点
for(ChainNode* u = p->next; u != NULL ; u = u->next)
{
if(u->data == j)
{
pre->next = pre->next->next;
delete u;
return;
}
else
{
pre = u;
}
}
}
void deleteEdge(int i ,int j, linkedDigraph &G)
{
deleteIJ(i,j,G);
deleteIJ(j,i,G);
}
- 【算法题】 假设有向图以邻接表存储,试编写算法删除弧(i,j)
// 第三题
struct ChainNode // 边表
{
int data;
ChainNode* next;
};
struct GraphChain //顶点表
{
int data;
ChainNode* firstNode;
};
struct linkedDigraph // 邻接表
{
ChainNode* aList;
int n; //顶点数
int e; //边数
};
void deleteIJ(int i, int j, linkedDigraph &G)
{
ChainNode* p = G.aList[i]->firstNode;
ChainNode* pre = p;
// 正好是头结点
if(p->data == j)
{
aList[i]->firstNode = p->next;
delete p;
return;
}
// 不是头结点
for(ChainNode* u = p->next; u != NULL ; u = u->next)
{
if(u->data == j)
{
pre->next = pre->next->next;
delete u;
return;
}
else
{
pre = u;
}
}
}
- 【算法题】已知有n个顶点的有向图的链接表,设计算法计算图中出度为0的顶点数
// 第三题
template <class T>
struct ChainNode // 边表
{
T data;
ChainNode<T>* next;
};
template <class T>
struct GraphChain //顶点表
{
T data;
ChainNode<T>* firstNode;
};
template <class T>
struct linkedDigraph // 邻接表
{
ChainNode<T>* aList;
int n; //顶点数
int e; //边数
};
template <class T>
int outDegree(linkedDigraph &G)
{
GraphChain<T>* aList = G.aList;
int n = numberOfVertices();
int num = 0;
for(int i = 1; i<=n; i++)
{
if(aList[i]->firstNode == NULL)
num++;
}
return num;
}
- 【算法题】邻接矩阵,仅保存下三角矩阵,编写算法计算给定顶点的度
// 第三题
int mapping(int i ,int j)
{
if(i >= j)
return i*(i-1)+j-1;
else
return j*(j-1)+i-1;
}
int getDegree(int k, int n, int a[])
{
int num = 0;
int tem;
for(int i = 0; i < k; i++)
{
tem = mapping(k, i);
if(a[tem] != 0)
num++;
}
for(int i = k+1; i < n; i++)
{
tem = mapping(k, i);
if(a[tem] != 0)
num++;
}
return num;
}
e. 掌握生成树的寻找方法
-
如果不是最小生成树,有BFS和DFS算法;
-
最小生成树有Prim和Kruskal算法;在算法设计中详细说明
十二、贪婪算法
a. 了解贪婪算法的基本概念
- 【潜在考点】描述什么叫做贪婪算法
在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。
b. 掌握AOV网的拓扑排序算法
- 与图一起考,写出拓扑排序序列
c. 掌握单源最短路径的Dijkstra算法
- 【图表题】根据图形,写出Dijkstra算法运行过程
d. 掌握最小耗费生成树的概念、Prim和Kruskal算法
- 在最小生成树的Kruskal算法中,如何判定是否存在回路?
使用并查集,如果新边的两个顶点同属于一个祖先,那么便形成回路。否则不形成回路
- 给出图、邻接链表或者邻接矩阵,求Prim或KrusKal的最小生成树
e. 了解AOE网的关键路径算法
- 根据AOE网的图,求关键路径
AOE网:顶点叫做事件、边叫做活动
这种比较简单,使用观察法便可以做出来
- 根据图表,画出AOE网,列出各事件最早、最晚发生时间;找出AOE中的关键路径,并回答完成该工程所需要的最短时间
有可能需要使用虚线
十三、分而治之算法
a. 了解分而治之思想
- 简述分而治之算法的思想
b. 掌握快速排序、归并排序实现方法
- 算法整理中有提到
c. 了解选择问题基本思想
十四、动态规划
a. 掌握所有顶点对时间的最短路径算法
- 给定图的邻接矩阵、邻接表或者图,写出FLoyd算法执行过程
十五、算法题梳理
这一部分按照线性表、树、图、排序算法、经典算法对可能的考点进行梳理,因为大部分代码都在本文或我的其它文章中整理好,只会写出小部分还未整理的问题的代码。目的就是没事看题目默写代码。
a. 线性表
考察链表或数组的插入、删除、查找操作以及链表的结构变换(指针操作),解决问题主要依靠这些操作,比如逆序、合并等等。同时还可能考察堆栈、堆等利用线性结构存储的数据结构
- 【链表删除】有一个单链表L,其结点的元素值以非递减有序排列,请编写函数purge,删除单链表中多余的元素值相同的结点
- 【数组删除】线性表使用公式化描述方式存储。编写一个函数,从给定的线性表A中删除值在x到y(包括x和y)之间的所有元素,要求以较高的效率来实现
- 【链表结构变换】为带表头的单链表Chain编写一个成员函数Reverse,该函数对链表进行逆序操作,要求逆序操作原地就地进行,不分配任何新的结点。要求首先给出类声明,在类的声明中,其它成员函数可省略
- 【链表插入】给出在带表头结点的单链表L中第i个结点(头结点为0)前插入一个关键字为x的节点的算法
//算法思想:分清两种情况,在头结点前插入,以及在第i个(i>0)个节点插入
template <class T>
struct ChainNode // 链表节点定义
{
T data;
ChainNode<T>* next;
};
template <class T>
bool insertX(ChainNode<T>* root, int i, T x) // 假设含有头结点,成功为true,失败为false
{
if(root == NULL || i < 0)
return false;
ChainNode<T>* p1 = new ChainNode<T>; // 要插入的新节点
p1->data = x;
p1->next = NULL;
ChainNode<T>* pre = root;
if(i == 0) // 在头结点前插入,将代替头结点
{
p1->next = root;
root = p;
return true;
}
else if(i > 0) // 在头结点之后插入
{
while(i>1 && pre->next!=NULL)
{
pre = pre->next;
i--;
}
}
if(pre->next != NULL) // 确保地i个节点存在
{
p1->next = pre->next;
pre->next = p1;
return true;
}
return false;
}
- 【链表查找】设有一个正整数序列组成的有序单链表(按递增次序有序,且允许有相等的整数存在),确定在序列中比正整数x大的数有几个
- 【链表删除】设有一个正整数序列组成的有序单链表(按递增次序有序,且允许有相等的整数存在),将单链表中比整数x小的偶数从单链表中删除
- 【数组查找-栈】称栈的初始状态和终态均为空,且可以操作从序列为合法序列,比如IOIIOIOO(I为进,O为出),设计算法判断序列是否为合法序列
- 【链表结构变换】已知递增有序的单链表A,B分别存储了一个集合,请设计算法以求出两个集合A和B的差A-B(在A中出现而不在B中出现的元素构成的集合),并返回A
template <class T>
struct ChainNode // 链表节点定义
{
T data;
ChainNode<T>* next;
};
template <class T>
void Sub_AB(ChainNode<T>* A, ChainNode<T>* B)// 假设含有头结点
{
ChainNode<T>* p1,p2;
if(B == NULL || B->next == NULL || A == NULL)
return;
p1 = A->next; // 遍历单链表A的指针
p2 = B->next; // 遍历单链表B的指针
pre = A; // p1的前驱指针
while(p1 != NULL && p2 != NULL)
{
if(p2->data < p1->data)
p2 = p2->next;
else if(p2->data > p1->data)
{
pre = p1;
p1 = p1->next;
}
else{
pre->next = p1->next;
delete p1;
p1 = pre->next;
}
}
}
- 【链表结构变换】已知递增有序的单链表A,B分别存储了一个集合,请设计算法以求出两个集合A和B的并集AUB,结果仍递增有序,并返回A
template <class T>
struct ChainNode // 链表节点定义
{
T data;
ChainNode<T>* next;
};
template <class T>
void merge_AB(ChainNode<T>* A, ChainNode<T>* B) // 假设含有头结点
{
ChainNode<T>* p1,p2,pre,p3;
if(B == NULL || B->next == NULL || A == NULL)
return;
p1 = A->next; // 遍历单链表A的指针
p2 = B->next; // 遍历单链表B的指针
pre = A;
B->next = NULL;
while(p1 != NULL && p2 != NULL)
{
p3 = p2->next;
if(p1->data > p2->data)
{
pre->next = p2;
p2->next = p1;
pre = p1;
p2 = p3;
p1 = p1->next;
}
else if(p1->data < p2->data)
{
pre = p1;
p1 = p1->next;
}
else
{
delete p2;
p2 = p3;
}
}
// 如果B先遍历完成,算法结束,如果A先遍历完成,将B剩余元素加到A后面
if(p2 == NULL)
return;
pre->next = p2;
}
- 【链表结构变换】L1与L2分别为两单链表头节点地址指针,且两表中数据结点的数据域均为一个字母。设计把L1中与L2中数据相同的节点顺序完全倒置的算法。 (L2中的连续字母在L1中只出现一次)
template <class T>
struct ChainNode // 链表节点定义
{
T data;
ChainNode<T>* next;
};
template <class T>
void merge_AB(ChainNode<T>* A, ChainNode<T>* B) // 假设含有头结点
{
ChainNode<T>* p1,p2,pre,p3;
if(B == NULL || B->next == NULL || A == NULL)
return;
p1 = A->next; // 遍历单链表A的指针
p2 = B->next; // 遍历单链表B的指针
pre = A;
B->next = NULL;
while(p1 != NULL && p2 != NULL)
{
p3 = p2->next;
if(p1->data > p2->data)
{
pre->next = p2;
p2->next = p1;
pre = p1;
p2 = p3;
p1 = p1->next;
}
else if(p1->data < p2->data)
{
pre = p1;
p1 = p1->next;
}
else
{
delete p2;
p2 = p3;
}
}
// 如果B先遍历完成,算法结束,如果A先遍历完成,将B剩余元素加到A后面
if(p2 == NULL)
return;
pre->next = p2;
}
- 【数组操作-堆】设计算法将顺序表序列调整为大根堆
- 【数组操作-堆】在已经排序好的大根堆顺序表序列中,插入一个元素,设计算法使插入后的序列仍为大根堆
b. 树
- 【递归遍历】最小深度是指根结点到最近的叶子结点的最短路径上的结点的个数,给定一个链式存储的二叉树,设计算法求其最小深度,叙述算法思想并分析算法的时间复杂度
- 【递归遍历】判断一棵树是否为最大树
- 【递归遍历&&非递归前序遍历】试编写算法,求给定二叉树上从根节点到叶子节点的一条路径长度等于树的深度减一的路径,若这样的路径存在多条,则输出路径终点在“最左”的一条
- 【递归遍历 || 非递归遍历】编写算法,求解从根节点到给定点p所指节点之间路径
- 【递归遍历 || 非递归遍历】计算二叉树中度为1的结点个数
- 【二叉搜索树】假设二叉搜索树利用二叉链表存储,编写算法,删除二叉搜索树的最小元素
- 【二叉搜索树】假设二叉搜索树利用二叉链表存储,编写算法,删除二叉搜索树中值为k的节点
- 【递归遍历】编写算法,判断一棵树是否为满二叉树
- 【非递归前序遍历 || 递归前序遍历】给出二叉树节点定义,并用先序遍历统计二叉树中叶子节点的个数
- 【非递归前序遍历 || 递归前序遍历】给出二叉树节点的定义,求给定节点q的父节点
- 【递归遍历】设计算法判断给定二叉树是否为二叉排序树
- 【递归遍历】设计算法,求二叉树中相距最远两个结点间的路径长度。
- 【递归遍历】假设一个仅包含二元运算符(+_*/)的算术表达式(只有度为0或2的节点)以二叉链表的形式存储在二叉树中,写出计算该算术表达式的算法
- 【递归遍历 || 非递归遍历】设计算法,求任意一颗二叉树的带权路径长度(非递归算法并不适合求解和层数有关的问题,比如层次遍历,记录在第几层往往需要利用两个int变量)
- 【之字遍历 && 双栈】设二叉树使用链式存储结构,之字型遍历二叉树
- 【层次遍历&&递归遍历】判断一棵树是否为最小堆(最小树&&完全二叉树)
- 【递归前中后遍历&&非递归前中后遍历&&层次遍历】
c. 图
- 【DFS || BFS】设计算法,判断图的邻接表中是否存在V_i到V_j的路径
- 【邻接表遍历】已知有向图以邻近表表示,编写算法,求指定顶点k的入度
- 【邻接表遍历 && 链表操作】已知无向图以邻接表存储,试编写算法删除边(i,j)
- 【邻接表遍历 && 链表操作】 假设有向图以邻接表存储,试编写算法删除弧(i,j)
- 【邻接表遍历】已知有n个顶点的有向图的链接表,设计算法计算图中出度为0的顶点数
- 【邻接矩阵遍历】邻接矩阵,仅保存下三角矩阵,编写算法计算给定顶点的度
- 【DFS || BFS】采用邻接表存储结构,编写一个判别无向图中任意给定的两个节点之间是否存在一条长度为k的简单路径的算法。
- 【DFS || BFS】对于给定无向图,采用邻接链表的存储方式,请编写算法标记出图中所有的连通分量
- 【邻接矩阵遍历 && 邻接表遍历】设计一个算法将无向图的邻接矩阵转为相应的邻接表
- 【邻接矩阵遍历 && 邻接表遍历】设计一个算法将无向图的邻接表转为相应的邻接矩阵
- 【DFS】已有邻接表表示的有向图,设计算法,输出从顶点u到顶点v的所有简单路径
- 【邻接矩阵遍历】假设n个顶点无向图以邻接矩阵存储,试编写算法输出与顶点i直接相连的顶点编号,其中1<=i<=n
- 【邻接表遍历 && 链表操作】已知有向图以邻接表存储,试编写算法增加弧(i,j)。
- 【BFS】设计算法求出无向联通图中距离顶点 V 0 V_0 V0的最短路径长度为K的所有的节点
d. 排序算法
考察排序算法,以及包含排序算法思想的问题。
- 【冒泡排序】给定一个整数数组,设计一个算法,将数组中的所有0元素全部移到数组末尾,保持其他非零元素相对位置不变,要求除数组本身外,不使用其他辅助存储空间
- 【快速排序】判断以@结尾的字符串是否为回文
- 【快速排序】设计算法实现将数组r[s:t] 中所有奇数移到所有偶数之前,要求算法复杂度为O(n),n为数组元素的个数
- 【选择排序】使用数组或链表实现选择排序
- 【插入排序】使用数组或链表实现插入排序
//以下为及时终止的冒泡排序,选择排序,插入排序,快速排序,名次排序的数组实现。如果有需要利用链表的题,可以利用算法思想仿写。
//及时终止的冒泡排序
void Bubble(int* a, int n) //n为数组元素个数
{
bool flag = true;
int tem;
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n-i-1; j++)
{
if(a[j] > a[j+1])
{
tem = a[j];
a[j] = a[j+1];
a[j+1] = tem;
flag = false;
}
}
if(flag == true)
return;
flag = true;
}
}
// 插入排序
void insert(int* a, int n) // n为数组元素个数
{
int tem;
int j;
for(int i = 1; i < n; i++)
{
tem = a[i];
for(j = i-1; j>=0;j--)
{
if(a[j] > a[i])
{
a[j] = a[j+1];
}else
{
a[j+1] = tem;
break;
}
}
if(j == -1)
a[0] = tem;
}
}
// 选择排序
void select(int* a, int n)
{
int minI, tem;
for(int i = 0; i < n-1; i++)
{
int minI = i;
for(int j = i+1; j < n;j++)
{
if(a[j] < a[minI])
minI = j;
}
if(minI != i)
{
tem = a[i];
a[i] = a[minI];
a[minI] = tem;
}
}
}
//快速排序
void QuickSort(int* a, int head, int tail)
{
if(tail > head)
{
int k = Partition(a, head, tail);
QuickSort(a, head, k-1);
QuickSort(a, k+1, tail);
}
}
int Partition(a, head, tail)
{
int pivot = a[head];
while(tail > head)
{
while(tail > head && a[tail] > pivot)
tail--;
a[head] = a[tail]; //第一次时,是支点元素,可以交换
while(tail > head && a[head] < pivot)
head++;
a[tail] = a[head]; //交换的是在右边的小于等于支点元素的值,可以交换
}
a[head] = pivot; //左指针和右指针相遇,支点元素换回
return head; // 返回划分的下标
}
// 归并排序
// 数组b以及申请好与a相同的空间
template <class T>
void mergeSort(T* a, T* b, int left, int right)
{
if(left > right)
{
int middle = (left + right)/2;
mergeSort(a, b, left, middle);
mergeSort(a, b, middle+1, right);
Merge(a, b, left, middle, right);
copy(b, a, left, right); // 数组复制函数
}
}
template <class T>
void Merge(T* a, T* b, int left, int middle, int right)
{
int first = left;
int second = middle+1;
int result = left;
while(first <= middle && second <= right)
{
if(a[first] > a[second])
b[result++] = a[second++];
else
b[result++] = a[first++];
}
if(first < middle)
{
while(first <= middle)
b[result++] = a[first++];
}else
{
while(second <= right)
b[result++] = a[second++];
}
}
//名次排序
template <class T> // 名次计算
void rankNum(T* a, int n, int* r)
{
for(int i = 0; i < n; i++)
{
r[i] = 0;
}
for(int i = 0; i < n; i++)
{
for(int j = 0; j < i; j++)
{
if(a[j] <= a[i])
r[i]++;
else
r[j]++;
}
}
}
// 名次排序
void rankSort(T* a, int n, int* r)
{
T* tem = new int[n];
rankNum(a,n,r);
for(int i = 0; i < n; i++)
tem[r[i]] = a[i];
for(int i = 0; i < n; i++)
a[i] = tem[i];
delete[] u;
}