数据结构与算法——查找与排序C++模板实现
文章目录
查找
顺序查找
从开头到尾的查找。O(n)
int Sequential_Search(int *a , int n,int key) {
int i ;
a[0] = key ; //哨兵
i = n ;
while(a[i]!= key)
--i;
return i;
}
有序表查找
折半查找
需要有序,线性表按顺序存储。对于频繁需要插入或删除操作的表,维护有序不建议折半查找。O(logn)
int Binary_Search(int *a,int n,int key) {
int low,high,mid ;
low = 1 ;
high = n ;
while(low<=high) {
mid = (low+high)/2;
if(a[mid]<key)
hign = mid-1;
else if ( key<a[mid])
low = mid+1;
else
return mid;
}
}
插值查找
表有序,也就是折半查找的改进,把折半改变成: 复杂度O(logn),适用于关键字分布均匀
m
i
d
=
l
o
w
+
k
e
y
−
a
[
l
o
w
]
a
[
h
i
g
h
]
−
a
[
l
o
w
]
(
h
i
g
h
−
l
o
w
)
mid = low + \frac{key-a[low]}{a[high]-a[low]}(high-low)
mid=low+a[high]−a[low]key−a[low](high−low)
斐波那契查找
表有序,利用黄金分割原理。
F
[
k
]
=
F
[
k
−
1
]
+
F
[
k
−
2
]
F[k] = F[k-1] + F[k-2]
F[k]=F[k−1]+F[k−2]
low与mid之间有F[k-1]-1个数,mid与high之间有F[k-2]-1个数,总的事F[k]-1个数。O(logn)
int Fib_Search(int *a,int n ,int key) {
int low = 1;
int high=n , mid ;
int k = 0 ;
while (n > F[k]-1)
++k;
for(int i=n ; i < F[k] ; ++i)
a[i] = a[n];
while( low <= high) {
mid = low + F[k-1] -1;
if( key < a[mid]) {
low = mid +1;
k = k -1 ;
} else if ( key > a[mid]) {
high = mid-1;
k = k-2 ;
} else{
if( mid <= n)
return mid;
else
return n;
}
}
return 0;
}
线性索引查找
索引就是一个关键字与它对应的记录相关联的过程。
线性索引就是将索引项集合组织为线性结构,也称为索引表。
稠密索引
稠密索引是指再线性索引中,将数据集中的每个记录对应一个索引项。
对于稠密索引这个索引表来说,索引项一定事按照关键码有序的排列。
空间代价大。
分块索引
需要满足两个条件:
- 块内无序
- 块间有序
分块索引的索引结分为三个数据项:
- 最大关键码,它之后的块的最小关键码也比这个大
- 存储了块中的记录个数
- 用于指向块首数据元素的指针,便于开始对这一块中记录进行遍历
块间折半查找,块内顺序查找。
倒排查找
其索引项的通用结构是:
- 次关键码,例如英文单词
- 记录号表,例如上面的文章编号
其中记录号表存储具有相同次关键字的所有记录的记录号(可以是指向记录的指针或者是该记录的主关键字)。
缺点是记录号可能不等长。
可以只记录差值,提高速率。
二叉排序树
性质:
- 左子树不空,则左子树上所有的节点的值均小于它的根结点的值
- 右子树不为空,则右子树上所有节点的值均大于它的根结点的值
- 它的左右子树也分别为二叉排序树
目的:为了提高查找和插入删除关键字的速度。
SearchBST
typedef struct BiTNode {
int data;
struct BiTNode *lchild,*rchild;
}BitNode,*BiTree;
bool SearchBST(BiTree T,int key,BiTree f,BitTree *p) {
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 {
reutrn SearchBST(T->rchild,key,T,p);
}
}
InsertBST
bool InsertBST(BiTree *T,int key) {
BiTree p,s;
if(!SearchBST(*T,key,nullptr,&p)){
s = new BiTNode(key,nullptr,nullptr) ;
if(!p)
*T = s;
else if ( key<p->data)
p->lchild = s;
else
p->rchild = s;
return true;
} else
return false;
}
对删除节点三种情况的分析:
- 叶子节点;
- 仅有左子树或右子树的节点;
- 左右子树都有的节点
DeleteBST
bool DeleteBST(BitTree *T,int key) {
if(!*T){
return false;
} else {
if ( key == (*T)->data){
return Delete(T) ;
else if ( key < (*T)->data)
return DeleteBST(&(*T)->lchild,key);
else
return DeleteBST(&(*T)->rchild,key);
}
}
bool Delete(BiTree *p) {
BiTree q,s;
if((*p)->rchild == NULL){
q=(*p)->lchild ; (*p)->data = q->data ; (*p)->lchild = q->lchild; (*p)->rchild = q->rchild;free(q);
} else if ((*p)->lchild == NULL) {
q=(*p)->rchild; (*p)->data = q->data;(*p)->lchild = q->lchild;(*p)->rchild = q->rchild ; free(q);
} else if ((*p)->rchild == NULL) {
q=*p ; *p=(*p)->lchild ; free(q);
} else {
q=*p ; s = (*p)->lchild;
while(s->rchild) {
q=s;s=s->rchild;
}
(*p)->data = s->data;
if(q!=*p)
q->rchild = s->lchild;
else
q->lchild = s->lchild;
free(s);
}
return true;
}
二叉排序树的查找性能取决于二叉排序树的形状。比较平衡的情况下为O(logn),最坏为O(n)
平衡二叉树(AVL树)
其中每一个节点的左子树和右子树的高度差至多等于1
将二叉树上节点的左子树深度减去右子树深度的值称为平衡因子BF,那么AVL树上的所有节点的BF只可能为-1、0和1
距离插入节点最近的,且平衡因子的绝对值大于1的节点为根的子树,我们称为最小不平衡子树。
基本思想:插入节点时,先检查是否破坏平衡,若是,找出最小不平衡子树。
记录每个节点的平衡因子,如果大于1就右旋。如果小于-1就左旋。
如果最小不平衡子树的根节点与它的子节点符号不同,需要双旋。
#include <iostream>
using namespace std;
template <typename T>
struct AVLTreeNode
{
AVLTreeNode(T value, AVLTreeNode<T>*l, AVLTreeNode<T>*r)
:key(value), lchild(l), rchild(r){}
T key;
int height;
AVLTreeNode<T>* lchild;
AVLTreeNode<T>* rchild;
};
template<typename T>
class AVLTree
{
public:
AVLTree(); //构造函数
~AVLTree(); //析构函数
void preOrder(); //前序遍历AVL树
void InOrder(); //中序遍历AVL树
void postOrder(); //后序遍历AVL树
void print(); //打印AVL树
void destory(); //销毁AVL树
void insert(T key); //插入指定值的节点
void remove(T key); //移除指定值的节点
AVLTreeNode<T>* search_recurse(T key); //利用递归算法进行指定值的查找
AVLTreeNode<T>* search_iterator(T key); //利用迭代算法进行指定值的查找
T minimum(); //返回AVL中的最小值
T maximum(); //返回AVL中的最大值
int height(); //返回树的高度
private:
AVLTreeNode<T>* root; //AVL树的根节点
private:
void preOrder(AVLTreeNode<T>* pnode) const;
void inOrder(AVLTreeNode<T>* pnode) const;
void postOrder(AVLTreeNode<T>* pnode) const;
void print(AVLTreeNode<T>* pnode,T key, int direction) const;
void destory(AVLTreeNode<T>* & pnode);
int height(AVLTreeNode<T>* pnode) ;
int max(int a, int b);
AVLTreeNode<T>* insert(AVLTreeNode<T>* &pnode, T key);
AVLTreeNode<T>* remove(AVLTreeNode<T>* & pnode, T key); //删除AVL树中节点pdel,并返回被删除的节点
AVLTreeNode<T>* minimum(AVLTreeNode<T>*pnode)const;
AVLTreeNode<T>* maximum(AVLTreeNode<T>*pnode)const;
AVLTreeNode<T>* search_recurse(AVLTreeNode<T>* pnode, T key) const;
AVLTreeNode<T>* search_iterator(AVLTreeNode<T>* pnode, T key) const;
AVLTreeNode<T>* leftRotation(AVLTreeNode<T>* pnode); //单旋:左旋操作
AVLTreeNode<T>* rightRotation(AVLTreeNode<T>* pnode); //单旋:右旋操作
AVLTreeNode<T>* leftRightRotation(AVLTreeNode<T>* pnode); //双旋:先左旋后右旋操作
AVLTreeNode<T>* rightLeftRotation(AVLTreeNode<T>* pnode); //双旋:先右旋后左旋操作
};
/*构造函数*/
template <typename T>
AVLTree<T>::AVLTree()
:root(nullptr){};
/*析构函数*/
template <typename T>
AVLTree<T>::~AVLTree()
{
destory(root);
}
/*返回两者中的较大者*/
template<typename T>
int AVLTree<T>::max(int a, int b)
{
return a > b ? a : b;
};
/*返回树中最大节点值*/
template <typename T>
AVLTreeNode<T>* AVLTree<T>::maximum(AVLTreeNode<T>* pnode)const
{
if (pnode != nullptr)
{
while (pnode->rchild != nullptr)
pnode = pnode->rchild;
return pnode;
}
return nullptr;
};
template<typename T>
T AVLTree<T>::maximum()
{
AVLTreeNode<T>* presult = maximum(root);
if (presult != nullptr)
return presult->key;
};
/*返回树中最小节点值*/
template <typename T>
AVLTreeNode<T>* AVLTree<T>::minimum(AVLTreeNode<T>* pnode)const
{
if (pnode != nullptr)
{
while (pnode->lchild != nullptr)
pnode = pnode->lchild;
return pnode;
}
return nullptr;
};
template<typename T>
T AVLTree<T>::minimum()
{
AVLTreeNode<T>* presult = minimum(root);
if (presult != nullptr)
return presult->key;
};
/*返回一棵树的高度*/
template <typename T>
int AVLTree<T>::height(AVLTreeNode<T>* pnode)
{
if (pnode != nullptr)
{
return pnode->height;
}
return 0; //如果是空树,高度为0
};
template <typename T>
int AVLTree<T>::height()
{
return height(root);
};
/*左旋转操作*/
/*pnode为最小失衡子树的根节点*/
/*返回旋转后的根节点*/
template<typename T>
AVLTreeNode<T>* AVLTree<T>::leftRotation(AVLTreeNode<T>* proot)
{
AVLTreeNode<T>* prchild = proot->rchild;
proot->rchild = prchild->lchild;
prchild->lchild = proot;
proot->height = max(height(proot->lchild),height(proot->rchild))+1; //更新节点的高度值
prchild->height = max(height(prchild->lchild), height(prchild->rchild)) + 1; //更新节点的高度值
return prchild;
};
/*右旋转操作*/
/*pnode为最小失衡子树的根节点*/
/*返回旋转后的根节点*/
template <typename T>
AVLTreeNode<T>* AVLTree<T>::rightRotation(AVLTreeNode<T>*proot)
{
AVLTreeNode<T>* plchild = proot->lchild;
proot->lchild = plchild->rchild;
plchild->rchild = proot;
proot->height = max(height(proot->lchild), height(proot->rchild)) + 1; //更新节点的高度值
plchild->height = max(height(plchild->lchild), height(plchild->rchild)) + 1; //更新节点的高度值
return plchild;
};
/*先左后右做旋转*/
/*参数proot为最小失衡子树的根节点*/
/*返回旋转后的根节点*/
template <typename T>
AVLTreeNode<T>* AVLTree<T>::leftRightRotation(AVLTreeNode<T>* proot)
{
proot->lchild= leftRotation(proot->lchild);
return rightRotation(proot);
};
/*先右旋再左旋*/
/*参数proot为最小失衡子树的根节点*/
/*返回旋转后的根节点*/
template<typename T>
AVLTreeNode<T>* AVLTree<T>::rightLeftRotation(AVLTreeNode<T>* proot)
{
proot->rchild = rightRotation(proot->rchild);
return leftRotation(proot);
};
/*插入操作*/
/*递归地进行插入*/
/*返回插入后的根节点*/
template <typename T>
AVLTreeNode<T>* AVLTree<T>::insert(AVLTreeNode<T>* &pnode, T key)
{
if (pnode == nullptr) //寻找到插入的位置
{
pnode = new AVLTreeNode<T>(key, nullptr, nullptr);
}
else if (key > pnode->key) //插入值比当前结点值大,插入到当前结点的右子树上
{
pnode->rchild = insert(pnode->rchild, key);
if (height(pnode->rchild) - height(pnode->lchild) == 2) //插入后出现失衡
{
if (key > pnode->rchild->key) //情况一:插入右子树的右节点,进行左旋
pnode=leftRotation(pnode);
else if (key < pnode->rchild->key) //情况三:插入右子树的左节点,进行先右再左旋转
pnode=rightLeftRotation(pnode);
}
}
else if (key < pnode->key) //插入值比当前节点值小,插入到当前结点的左子树上
{
pnode->lchild = insert(pnode->lchild, key);
if (height(pnode->lchild) - height(pnode->rchild) == 2) //如果插入导致失衡
{
if (key < pnode->lchild->key) //情况二:插入到左子树的左孩子节点上,进行右旋
pnode = rightRotation(pnode);
else if (key>pnode->lchild->key)
pnode = leftRightRotation(pnode);//情况四:插入到左子树的右孩子节点上,进行先左后右旋转
}
}
pnode->height = max(height(pnode->lchild), height(pnode->rchild)) + 1;
return pnode;
};
template <typename T>
void AVLTree<T>::insert(T key)
{
insert(root, key);
};
/*递归查找指定元素*/
template <typename T>
AVLTreeNode<T>* AVLTree<T>::search_recurse(T key)
{
return search_recurse(root,key);
};
template <typename T>
AVLTreeNode<T>* AVLTree<T>::search_recurse(AVLTreeNode<T>* pnode, T key) const
{
if (pnode != nullptr)
{
if (key == pnode->key)
return pnode;
if (key > pnode->key)
return search_recurse(pnode->rchild,key);
else
return search_recurse(pnode->lchild,key);
}
return nullptr;
};
/*非递归查找指定元素*/
template <typename T>
AVLTreeNode<T>* AVLTree<T>::search_iterator(T key)
{
return search_iterator(root, key);
};
template <typename T>
AVLTreeNode<T>* AVLTree<T>::search_iterator(AVLTreeNode<T>* pnode, T key) const
{
while (pnode != nullptr)
{
if (pnode->key == key)
return pnode;
else if (key > pnode->key)
pnode = pnode->rchild;
else
pnode = pnode->lchild;
}
return nullptr;
};
/*删除指定元素*/
template<typename T>
AVLTreeNode<T>* AVLTree<T>::remove(AVLTreeNode<T>* & pnode, T key)
{
if (pnode != nullptr)
{
if (key == pnode->key) //找到删除的节点
{
//因AVL也是二叉排序树,删除节点要维护其二叉排序树的条件
if (pnode->lchild != nullptr&&pnode->rchild != nullptr) //若左右都不为空
{
if (height(pnode->lchild) > height(pnode->rchild)) //左子树比右子树高
{
//使用左子树最大节点来代替被删节点,而删除该最大节点
AVLTreeNode<T>* ppre = maximum(pnode->lchild); //左子树最大节点
pnode->key = ppre->key; //将最大节点的值覆盖当前结点
pnode->lchild = remove(pnode->lchild, ppre->key); //递归地删除最大节点
}
else
{
//使用最小节点来代替被删节点,而删除该最小节点
AVLTreeNode<T>* psuc = minimum(pnode->rchild); //右子树的最小节点
pnode->key = psuc->key; //将最小节点值覆盖当前结点
pnode->rchild = remove(pnode->rchild, psuc->key); //递归地删除最小节点
}
}
else
{
AVLTreeNode<T> * ptemp = pnode;
if (pnode->lchild != nullptr)
pnode = pnode->lchild;
else if (pnode->rchild != nullptr)
pnode = pnode->rchild;
delete ptemp;
return nullptr;
}
}
else if (key > pnode->key) //要删除的节点比当前节点大,则在右子树进行删除
{
pnode->rchild = remove(pnode->rchild, key);
if (height(pnode->lchild) - height(pnode->rchild) == 2) //删除右子树节点导致不平衡:相当于情况二或情况四
{
if (height(pnode->lchild->rchild)>height(pnode->lchild->lchild))
pnode = leftRightRotation(pnode); //相当于情况四
else
pnode = rightRotation(pnode); //相当于情况二
}
}
else if (key < pnode->key) //要删除的节点比当前节点小,则在左子树进行删除
{
pnode->lchild= remove(pnode->lchild, key);
if (height(pnode->rchild) - height(pnode->lchild) == 2) //删除左子树节点导致不平衡:相当于情况三或情况一
{
if (height(pnode->rchild->lchild)>height(pnode->rchild->rchild))
pnode = rightLeftRotation(pnode);
else
pnode = leftRotation(pnode);
}
}
return pnode;
}
return nullptr;
};
template<typename T>
void AVLTree<T>::remove(T key)
{
root =remove(root, key);
};
/*中序遍历*/
template<typename T>
void AVLTree<T>::inOrder(AVLTreeNode<T>* pnode) const
{
if (pnode != nullptr)
{
inOrder(pnode->lchild);
cout << pnode->key << endl;
inOrder(pnode->rchild);
}
};
template<typename T>
void AVLTree<T>::InOrder()
{
inOrder(root);
};
/*前序遍历*/
template<typename T>
void AVLTree<T>::preOrder(AVLTreeNode<T>* pnode) const
{
if (pnode != nullptr)
{
cout << pnode->key << endl;
inOrder(pnode->lchild);
inOrder(pnode->rchild);
}
};
template<typename T>
void AVLTree<T>::preOrder()
{
preOrder(root);
};
/*后列遍历*/
template<typename T>
void AVLTree<T>::postOrder(AVLTreeNode<T>* pnode) const
{
if (pnode != nullptr)
{
inOrder(pnode->lchild);
inOrder(pnode->rchild);
cout << pnode->key << endl;
}
}
template<typename T>
void AVLTree<T>::postOrder()
{
postOrder(root);
};
/*销毁AVL树*/
template<typename T>
void AVLTree<T>::destory(AVLTreeNode<T>* & pnode)
{
if (pnode != nullptr)
{
destory(pnode->lchild);
destory(pnode->rchild);
delete pnode;
pnode = nullptr;
}
};
template<typename T>
void AVLTree<T>::destory()
{
destory(root);
}
int main(int argc, char* argv[])
{
AVLTree<int> a;
for (int i = 0; i < 10; i++)
a.insert(i);
cout << "树高:" << a.height() << endl;
cout << "中序遍历:" << endl;
a.InOrder();
cout << "删除元素10"<<endl;
a.remove(10);
AVLTreeNode<int>* b = a.search_iterator(10);
if (b != nullptr)
cout << b->key;
else
cout << "无此元素" << endl;
getchar();
return 0;
}
删除Remove还有问题。
2-3树
多路查找树,用于内存读取外存:其中每一个结点都具有两个孩子或三个孩子。
一个2节点有一个元素两个孩子。必须同时有两个孩子或没有孩子。
一个3节点有一小一大两个元素,以及3个孩子。必须同时具有3个孩子或没有孩子。左子树包含小于较小元素的元素,右子树包含大于较大元素的元素,中间子树在两者之间的数。
2-3树插入分为三种情况。
插入元素需要拆分的,将树中两元素或插入元素的三者中选择其一向上移动一层。
2-3树插入的传播效应导致了根节点的拆分,则树的高度就会增加。
2-3树删除也分为三种情况。
- 删除一个底端3叶节点的元素。
- 删除的元素位于一个2节点上,需要分4种情况
- 右节点也是个2节点,把双亲的双亲拿下来,让双亲的双亲的右子树里最小的补位。
- 双亲不能是3节点的了,拆分,合成,将叶节点合成一个
- 右节点是个3节点,左旋
- 满二叉树的情况下,要减少层数。
- 所删除的元素位于非叶子节点处。此时我们通常是将树按中序遍历后得到此元素的前驱或后继元素,考虑它们补位即可。
2-3-4树
一个4节点包含小中大三个元素和四个孩子(或没有孩子)
B树
B树是一种平衡的多路查找树。2-3树和2-3-4树都是B树的特例。节点最大的孩子数目称为B树的阶。
B树属性: 每一个非根的分支节点都有k-1个元素和k个孩子。每个叶子节点n都有k-1个元素。
⌈
m
/
2
⩽
k
⩽
m
⌉
\lceil m/2 \leqslant k \leqslant m \rceil
⌈m/2⩽k⩽m⌉
所以与二叉树的操作不同,它们减少了必须访问节点和数据块的数量,从而提高了性能。可以说,B树的数据结构就是为内外存的数据交互准备的。
最坏情况查找多少次:
l
o
g
⌈
m
2
⌉
(
n
+
1
2
)
+
1
log_{\lceil {\frac{m}{2}} \rceil}(\frac{n+1}{2})+1
log⌈2m⌉(2n+1)+1
B+树
解决B树的遍历问题。严格意义上已经不是树。
在B+树中,出现在分支节点中的元素会被当作它们在该分支节点位置的中序后继者(叶子节点)中再次列出。另外,每一个叶子节点都会保存一个指向后一叶子节点的指针。
3 | 5 | 8
1 | 2 | 3 -> |4| 5| -> |6|7|8| -> |9|
m阶的B+树和m阶的B树的差异在于:
- 有n棵子树的节点中包含有n个关键字;
- 所有的叶子节点包含全部关键字的信息,及指向含这些关键字记录的指针,叶子节点本身依关键字的大小自小而大顺序链接
- 所有分支节点可以看成是索引,节点中仅含有其子树中的最大(或最小)关键字。
如果要随机查找,我们就从根节点出发,与B树的查找方式相同,只不过即使在分支节点找到了待查找的关键字,它也只是用来索引的,不能提供实际记录的访问,还是需要到达包含此关键字的终端节点。
B+树的插入、删除过程也都与B树类似,只不过插入和删除的元素都是在叶子节点上进行而已。
散列表查找
采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表。
- 直接定址法
- 数字分析法。常用抽取,抽取方法是使用关键字的一部分来计算散列存储位置的方法。
- 平方取中法。适合于不知道关键字的分布,而位数不大的情况
- 折叠法是将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,去最后几位作为散列地址。不知道关键字的分布,适合关键字位数较多的情况。
- 除留余数法:不仅可以对关键字直接取模,也可以在折叠、平方取中后取模。模p的选择很重要。因此根据前辈门的经验,若散列表表长为m,通常P为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数
- 随机数法:当关键字不等长时,采用这个方法构造散列函数比较合适。
散列冲突的方法
- 开放定址法:一旦发生了冲突,就取寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
f i ( k e y ) = ( f ( k e y ) + d i ) M O D m ( d i = 1 , 2 , 3 , . . . , m − 1 ) f_i(key) = (f(key)+d_i) MOD \ \ m \ \ \ (d_i = 1,2,3,...,m-1) fi(key)=(f(key)+di)MOD m (di=1,2,3,...,m−1)
di可以进行改进,正负,或随机
-
再散列函数法:准备多个散列函数,发生冲突就换一个散列函数计算。这种方法能够使得关键字不产生聚集,但增加了计算时间。
f i ( k e y ) = R H i ( k e y ) ( i = 1 , 2 , . . . , k ) f_i(key) = RH_i(key) (i = 1,2,...,k) fi(key)=RHi(key)(i=1,2,...,k) -
链地址法:将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。
-
公共溢出区法:先在基本表比对,没有相等才去溢出表查找。有冲突数据很少的情况下,查找性能很高。
散列表的查找性能
- 散列函数是否均匀
- 处理冲突的方法
- 散列表的装填因子:装填因子=填入表中的记录个数/散列表的长度
排序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zi0CUYbH-1599808796813)(C:\Users\asus\Desktop\C++\pictures\1.png)]
排序稳定性
在有关键字相等的情况下,每次排序结果都是一样的,则称为稳定。
待排序的记录是否全部被放置在内存中,排序分为:内排序和外排序。
- 时间性能
- 辅助空间:除了存放待排序所占用的存储空间之外,执行算法所需要的其他存储空间
- 算法的复杂度
内排序分为:插入排序,交换排序,选择排序和归并排序
按照算法的复杂度分为两大类:冒泡排序、简单选择排序和直接插入排序属于简单算法,而希尔排序、堆排序、归并排序、快速排序属于改进算法。
冒泡排序
冒泡排序是一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序就交换,直到没有反序的记录为止。复杂度:
O
(
n
2
)
O(n^2)
O(n2)
template <typename T>
void BubbleSort0(vector<T> &vec) {
T temp;
for(int i = 0 ; i < vec.size() ;++i) {
T current = vec[i];
for(int j = i ; j <vec.size() ; ++j){
if(current > vec[j]){
temp = current ;
current = vec[j];
vec[j] = temp;
}
}
}
}
template <typename T>
void BubbleSort0(vector<T> &vec) {
T temp;
for(int i = 0 ; i < vec.size() ;++i) {
for(int j = vec.size()-2 ; j >= i ; --j){
if(vec[j] > vec[j+1]){
temp = vec[j+1] ;
vec[j+1] = vec[j];
vec[j] = temp;
}
}
}
}
优化:
template <typename T>
void BubbleSort0(vector<T> &vec) {
T temp;
bool flag = true;
for(int i = 0 ; i < vec.size() ;++i) {
flag = false ;
for(int j = vec.size()-2 ; j >= i ; --j){
if(vec[j] > vec[j+1]){
temp = vec[j+1] ;
vec[j+1] = vec[j];
vec[j] = temp;
flag = true;
}
}
}
}
直接插入排序
将元素插入到排好序的有序表中,从而得到一个新的、记录数增1的有序表。
首先,将插入值和最后一个值比较,如果插入值小于最后一个值,就寻找该值应该所处的位置,并后移。
O
(
n
2
)
O(n^2)
O(n2)
template<typename T>
void insertSort(vector<T>& vec){
for(int i = 1 ; i !=vec.size() ; ++i) {
int j;
if( vec[i] < vec[i-1]){
T temp = vec[i];
for(j = i-1; vec[j] > temp && j>=0 ; --j) {
vec[j+1] = vec[j];
}
vec[j+1] = temp;
}
}
}
希尔排序
对直接插入排序改进。将大的记录量分割成若干个子序列,此时每个子序列待排序的记录个数比较少,然后在这些子序列内分别进行直接插入排序,当整个序列都基本有序时,注意是基本有序,再对全体进行一次直接插入排序。
所谓基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间。
我们需要采用跳跃分割的策略:将相距某个"增量"的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。时间复杂度:
O
(
n
3
2
)
O(n^\frac{3}{2})
O(n23)
注意:增量序列的最后一个增量必须等于1才可以。
template<typename T>
void ShellSort(vector<T> &vec) {
int increment = (int)vec.size();
do {
increment = increment/2 ; //需要自己修改这个常数,保证最后是1
for(int i = increment ; i != (int)vec.size() ; ++i) {
if(vec[i] < vec[i-increment]){
//定位到大于该值的位置,然后跳跃递减
for(int j = i-increment ; j>=0 && vec[j] >vec[j+increment] ; j = j - increment) {
T temp = vec[j+increment];
vec[j+increment] = vec[j];
vec[j] = temp;
}
}
}
} while( increment>1);
}
堆排序
在选到最小值的同时,并根据比较结果对其他记录做出相应的调整,那样排序的总体效率就会非常高。
堆是具有以下性质的完全二叉树:
- 每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆
- 或者每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆
k i ≤ k 2 i , k i ≤ k 2 i + 1 k_i \leq k_{2i} , k_i \leq k_{2i+1} ki≤k2i,ki≤k2i+1
堆排序的基本思想:利用堆进行排序,假设大顶堆,将整个数据形成一个堆,然后将堆顶的根节点与堆数组的末尾元素进行交换,再从剩下的元素中重新构造一个堆。时间复杂度:不稳定排序
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
template<typename T>
void HeapAdjust(vector<T>& vec,int s,int m) {
T temp = vec[s];
for(int i = s*2+1 ; i <= m ; i= i*2+1){
if( i!=m && vec[i]<vec[i+1]){
i++;
}
if( temp >= vec[i])
break;
vec[s] = vec[i];
s = i;
}
vec[s] = temp ;
}
template<typename T>
void HeapSort(vector<T>& vec) {
for(int i = (vec.size()-1)/2 ; i>=0 ; --i)
HeapAdjust(vec,i,vec.size()-1);
for(int i = (vec.size()-1) ; i > 0 ; --i){
swap(vec[i],vec[0]);
HeapAdjust(vec,0,i-1);
}
}
归并排序
数据结构中的定义是将两个或两个以上的有序表组合成一个新的有序表。
**基本原理:**假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到
⌈
n
/
2
⌉
\lceil n/2 \rceil
⌈n/2⌉
个长度为2或1的有序子序列;再两两归并,再重复,直到得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。
递归的解法:
template <typename T>
void Merge(vector<T> &fromVec,vector<T> &toVec,int low,int mid,int high){
int k;
int j;
for(j=mid+1,k=low ; low<=mid && j<=high ;++k) {
if(fromVec[low] < fromVec[j])
toVec[k] = fromVec[low++];
else
toVec[k] = fromVec[j++];
}
if(low<=mid){ //补全
for(int l=0 ; l<=mid-low;l++)
toVec[k+l] = fromVec[low+l];
}
if(j<=high){ //补全
for(int l=0 ; l<=high-j;l++)
toVec[k+l] =fromVec[j+l];
}
}
template<typename T>
void MSort(vector<T> &vec1,vector<T> &vec2,int low,int high){
int mid;
vector<T> vec3(vec1.size(),T());
if(low==high)
vec2[low]=vec1[low];
else{
mid = (low+high)/2;
MSort(vec1,vec3,low,mid);
MSort(vec1,vec3,mid+1,high);
for(auto &v:vec3){
cout << v <<" " ;
} cout << endl;
Merge(vec3,vec2,low,mid,high);
}
}
template<typename T>
void MergeSort(vector<T> &vec) {
MSort(vec,vec,0,vec.size()-1);
}
递归的时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
递归的空间复杂度:
O
(
n
+
l
o
g
n
)
O(n+logn)
O(n+logn)
归并排序比较占用内存,但是效率很高且稳定。
非递归解法:
template<typename T>
void Merge(vector<T> &fromVec,vector<T> &toVec,int low, int mid, int high){
int k;
int j;
for(k=low,j=mid+1 ; low<=mid && j <=high ;++k ){
if(fromVec[low]<fromVec[j])
toVec[k] = fromVec[low++];
else
toVec[k] = fromVec[j++];
}
if(low<=mid){
for(int l=0 ; l<=mid-low ; l++)
toVec[k+l] = fromVec[low+l];
}
if(j<=high){
for(int l=0 ; l<=high-j ; l++){
toVec[k+l] = fromVec[j+l];
}
}
}
template<typename T>
void MergePass(vector<T> &vec1,vector<T> &vec2,int s,int e) {
int i = 0;
int pos = (s==0 ? 1 : s*2);
for( ; i <= e -2*pos +1 ; i = i+2*pos){
Merge(vec1,vec2,i,i+pos-1,i+2*pos-1);
}
if( i < e-pos+1) { //表示最后只剩下一个pos和一个单一序列
Merge(vec1,vec2,i,i+pos-1,e);
}
else //表示最后只剩下一个单一序列
for(int j = i ; j<=e ; ++j)
vec2[j] = vec1[j];
}
template<typename T>
void MergeSort(vector<T> &vec1) {
int k = 0 ;
vector<T> tvec(vec1.size(),T());
while(k<tvec.size()){
MergePass(vec1,tvec,k,vec1.size()-1);
k = (k==0 ? 1: k*2);
MergePass(tvec,vec1,k,vec1.size()-1);
k*=2;
}
}
非递归法空间复杂度为:
O
(
n
)
O(n)
O(n)
应该尽量使用非递归的方法。
快速排序
冒泡的升级,交换排序类。通过不断比较和移动交换来实现排序的,只不过它的实现,增大了记录的比较和比较的距离,将关键字较大的记录从前面直接移动到后面,关键字较小的记录从后面直接移动到前面,从而减少了总的比较次数和移动交换次数。
**基本思想:**通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
template<typename T>
int Partition(vector<T> &vec,int low ,int high) {
T pivotkey = vec[low];
while( low < high) {
while(low<high && vec[high]>=pivotkey)
high--;
swap(vec[high],vec[low]);
while(low<high && vec[low]<=pivotkey)
low++;
swap(vec[low],vec[high]);
}
return low;
}
template <typename T>
void QSort(vector<T> &vec,int low,int high) {
int pivot;
if(low<high){
pivot = Partition(vec,low,high);
QSort(vec,low,pivot-1);
QSort(vec,pivot+1,high);
}
}
template<typename T>
void QuickSort(vector<T> &vec) {
QSort(vec,0,vec.size()-1);
}
在最优的情况下,快速排序算法的时间复杂度是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
在最坏的情况下,快速排序算法的时间复杂度是
O
(
n
2
)
O(n^2)
O(n2)
最坏情况的空间复杂度:
O
(
n
)
O(n)
O(n)
平均情况的空间复杂度:
O
(
l
o
g
n
)
O(logn)
O(logn)
快排的优化
应随机获得一个low与high之间的随机数。让这个rnd与low互换,这被称为随机选取枢轴法。
三数取中法,即取三个关键字先进行排序,将中间数作为枢轴,一般是取左端、右端和中间三个数,也可以随机选取。一般随机选取不考虑。还有九数取中法。
template<typename T>
int Partition(vector<T> &vec,int low ,int high) {
int mid = low + (high-low)/2;
if(vec[low]>vec[high])
swap(vec[low],vec[high]);
if(vec[mid]>vec[high])
swap(vec[mid],vec[high]);
if(vec[mid]>vec[low])
swap(vec[low],vec[mid]);
T pivotkey = vec[low];
while( low < high) {
while(low<high && vec[high]>=pivotkey)
high--;
swap(vec[high],vec[low]);
while(low<high && vec[low]<=pivotkey)
low++;
swap(vec[low],vec[high]);
}
return low;
}
优化不必要的交换:
template<typename T>
int Partition(vector<T> &vec,int low ,int high) {
int mid = low + (high-low)/2;
if(vec[low]>vec[high])
swap(vec[low],vec[high]);
if(vec[mid]>vec[high])
swap(vec[mid],vec[high]);
if(vec[mid]>vec[low])
swap(vec[low],vec[mid]);
T pivotkey = vec[low];
while( low < high) {
while(low<high && vec[high]>=pivotkey)
high--;
vec[low]=vec[high];
while(low<high && vec[low]<=pivotkey)
low++;
vec[high]=vec[low];
}
vec[low] = pivotkey;
return low;
}
3.优化小数组时的排序方案:
数组非常小的时候,快排不如直接插入排序,原因是快排用到了递归操作,在大量数据排序时,这点性能可以忽略。
#define MAX_LENGTH 7
template <typename T>
void QSort(vector<T> &vec,int low,int high) {
int pivot;
if((high-low)>MAX_LENGTH){
pivot = Partition(vec,low,high);
QSort(vec,low,pivot-1);
QSort(vec,pivot+1,high);
}
else {
insertSort(vec);
}
}
4.优化递归操作
每次递归调用耗费一定栈空间,如果能减少递归,将提高性能。对QSort实施尾递归优化。采用迭代的方式而不是递归的方法可以缩减堆栈深度。
template <typename T>
void QSort(vector<T> &vec,int low,int high) {
int pivot;
if((high-low)>MAX_LENGTH){
while(low<high) {
pivot = Partition(vec,low,high);
QSort(vec,low,pivot-1);
low = pivot+1;}
}
else {
insertSort(vec);
}
}
基数排序
基本思想:不进行关键字的比较,以十进制为例,基数指的是数的位。
- Least significant digit(LSD):短的关键字被认为是小的,排在前面,然后相同长度的关键字再按照词典顺序或者数字大小等进行排序。
- Most significant digit(MSD):直接按照字典的顺序进行排序,对于字符串、单词或者是长度固定的整数排序比较合适。
流程:先从低位开始放入桶,再不断上升。
算法分析:采用链接分配是合理的。额外空间的需求为n,再增加一个指向的首位的指针就可以
一般情况下,每个结点有d位关键字,必须执行d次分配和收集操作。
- 每次分配的代价:O(n)
- 每次收集的代价:O(radix(基数))
- 总代价:O(d*(n+radix))
倒序遍历保证稳定性。
void print(int *a,int sz) {
for(int i = 0; i < sz; i++)
cout << a[i] << " ";
cout << endl;
}
void radixSort(int *a,int arraySize,int radix) {
int bucket[radix];
int digitPos = 1;
int maxVal = 0;
for(int i = 0 ; i < arraySize ; ++i)
if(a[i]>maxVal)
maxVal = a[i];
while(maxVal/digitPos >0) {
int digitCount[radix] = {0};
for(int i = 0 ; i < arraySize ; ++i ){
digitCount[a[i]/digitPos%10]++;
}
/* accumulated count */
for(int i = 1 ; i <radix;++i)
digitCount[i] += digitCount[i-1];
/* To keep the order, start from back side */
for(int i =arraySize-1 ; i>=0 ; --i){
bucket[--digitCount[a[i]/digitPos%10]] = a[i];
}
for(int i = 0 ; i < arraySize ; ++i){
a[i] = bucket[i] ;
}
digitPos *= radix;
print(a,arraySize);
}
桶排序
基本思想:桶排序的基本思想是将一个数据表分割成许多buckets,然后每个bucket各自排序,或用不同的排序算法,或者递归的使用bucket sort算法。也是典型的divide-and-conquer分而治之的策略。它是一个分布式的排序,介于MSD基数排序和LSD基数排序之间。
基本流程:
- 建立一堆buckets;
- 遍历原始数组,并将数据放入到各自的buckets当中;
- 对非空的buckets进行排序;
- 按照顺序遍历这些buckets并放回到原始数组中即可构成排序后的数组。
#define NBUCKETS 5
#define INTERVAL 10
struct Node {
int data;
struct Node* next;
};
/* Insertion Sort */
Node* insertSort(Node* list) {
struct Node* k,*nodeList;
if( list == nullptr || list->next == nullptr){
return list;
}
nodeList = list;
k = nodeList->next;
nodeList->next = nullptr ; //the 1st inserted node
while(k != nullptr) {
struct Node* ptr;
if(nodeList->data > k->data){
struct Node* tmp;
tmp = k;
k = k->next;
tmp->next = nodeList;
nodeList = tmp;
continue;
}
for( ptr = nodeList ; ptr->next != nullptr ; ptr = ptr->next){ //定位指向
if(ptr->next->data > k->data) break;
}
if(ptr->next != nullptr) {//如果存在前一个指向比后一个指向小
struct Node* tmp ;
tmp = k;
k = k->next;
tmp->next = ptr->next;
ptr->next = tmp;
continue;
}
else{//前一个指向比后一个指向的值大。
ptr->next = k;
k = k->next;
ptr->next->next = nullptr;
continue;
}
}
return nodeList;
}
void print(int ar[],int sz)
{
int i;
for(i = 0; i < sz; ++i) {
cout << " " << ar[i];
}
cout << endl;
}
void bucketSort(int *arr,int sz) {
struct Node **buckets = new struct Node*[NBUCKETS];
for(int i = 0 ; i < NBUCKETS ; ++i) {
buckets[i] = nullptr;
}
for(int i = 0 ; i < sz ; ++i) {
struct Node* temp = new Node;
int pos = arr[i]/INTERVAL ;
temp->data = arr[i];
temp->next = buckets[pos];
buckets[pos] = temp;
}
for(int i = 0 ; i<NBUCKETS; ++i){
buckets[i]=insertSort(buckets[i]);
}
int count = 0;
for(int i=0 ; i<NBUCKETS ;++i){
for(struct Node* node=buckets[i] ; node != nullptr ; node = node->next)
arr[count++] = node->data;
}
for(int i = 0; i < NBUCKETS;++i) {
struct Node *node;
node = buckets[i];
while(node) {
struct Node *tmp;
tmp = node;
node = node->next;
delete tmp;
}
}
delete [] buckets;
}
计算排序
基本思想:对数据表长度为n,利用比较元素进行排序的方法
基本操作:根据获得的数据表的范围, (根据最大值、最小值等)分割成不同的buckets,然后直接统计数据在buckets上的频次,然后顺序遍历buckets就可以得到已经排好序的数据表。
void countingSort(int *arr, int arraySize) {
int max,min;
int index = 0;
max = min = arr[0];
for ( int i = 1 ; i < arraySize ; ++i) {
if(arr[i]>max){
max = arr[i];
}
if(arr[i]<min){
min = arr[i];
}
}
int k = max - min + 1 ;
int *bucket = new int[k];
for( int j = 0 ; j<k ; j++){
bucket[j] = 0 ;
}
for(int i = 0 ; i <arraySize ; ++i){
bucket[arr[i]-min]++;
}
for(int i = min ; i<=max ;++i){
for(int j = 0 ; j<bucket[i-min];++j){
arr[index++] = i;
}
}
print(arr,arraySize);
delete [] bucket;
}
测试主程序:
int main(void)
{
int array[8] = {29,25,3,49,9,37,21,43};
cout << "Initial array" << endl;
print(array,8);
cout << "-------------" << endl;
bucketSort(array,8);
cout << "-------------" << endl;
cout << "Sorted array" << endl;
print(array,8);
system("pause");
return 0;
}