一、数据结构绪论
数据结构的基本概念
数据结构是一门研究非数值计算的程序设计问题中,计算机的操作对象以及它们之间的关系和操作的学科。
数据元素是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。
数据结构包含三个方面的含义:
逻辑结构:
物理结构:数据的逻辑结构在计算机中的表示,称此为物理结构,或称存储结构。
数据类型:一个值的集合以及定义在这个值集上的一组操作的总称。
抽象数据类型:通常由用户定义,用以表示应用问题的数据模型以及定义在该模型上的一组操作。
算法是描述计算机解决给定问题的操作过程,即为决解某一特定问题而由若干条指令组成的有穷序列。
算法的效率分析:
http://univasity.iteye.com/blog/1164707
事后统计法:收集该算法实际的执行时间和实际占用空间的统计资料。
事前分析估算法:在算法运行之前分析该算法的时间复杂度和空间复杂度,来判断算法的效率。
时间复杂度分析:
、
常见函数的时间复杂度按数量递增排列及增长率:
二、线性表
线性表的类型定义
线性表是n(n>0)个相同类型数据元素构成的有限序列,其中n为线性表的长度。
线性表的基本操作:
线性表的顺序表示和实现
线性表的顺序存储结构:用一组地址连续的存储单元依次存储线性表的元素。
线性表的顺序存储,也成为向量存储,又可以说是一维数组存储。线性表中结点存放的物理顺序与逻辑顺序完全一致,它叫向量存储。
线性表顺序存储结构在插入或删除数据元素时比较繁琐,但是它比较适合存取数据元素。
线性表的插入操作:在第i个元素之前插入一个元素时,需将第n至第i(共n-i+1)个元素向后移动一个位置。
线性表的删除操作:删除第i个元素时需将从第i+1至第n(共n-i)个元素一次向前移动一个位置
线性表的链式表示和实现
用一组任意的存储单元(可能不连续)存储线性表的数据元素。
在链式存储结构中,每个存储结点不仅包含数据元素本身的信息,还必须包含每个元素之间逻辑关系的信息,即包含直接后继结点的地址信息(指针域)。
逻辑顺序与物理顺序有可能不一致;属顺序存取的存储结构,即存取每个元素必须从第一个元素开始遍历,直到找到需要访问的元素,所以所花时间不一定相等。
链表的创建方式:
结点类的定义:
单链表的基本操作
插入方式——头插法:
插入方式——尾插法:
查找运算——按序号查找:在链表中,即使知道被访问结点的序号i,也不能像顺序表中那么直接按序号i访问结点,而只能从链表的头指针除法,顺着链域next逐个结点往下搜索,直至搜索到第i个结点为止。链表不是随机存取结构,只能顺序存取。
查找运算——按数值查找:
删除结点:将被删除结点的前驱指针连接被删除结点的后继指针
循环链表
表中尾结点的指针域指向头结点,形成一个环。从表中任意一个点出发都可以找到表中其他的结点。
循环链表的操作和线性链表的操作基本一致,但循环链表中没有NULL指针,故遍历操作时,终止条件不再是判断p或p.next是否为空,而是判断他们是否等于某一指定指针,如头指针或尾指针。
双向链表
双向链表是在单链表的每个结点里再增加一个指向其直接前驱的指针域prior。这样就形成了链表中有两个方向不同的链,故称为双向链表。
双线链表——头插法:
双向链表——尾插法:
插入操作
删除操作
三、栈和队列
栈的概念
栈是限制在表的一端进行插入和删除运算的线性表,通常称插入、删除的这一端为栈顶,另一端为栈底。当表中没有元素时成为空栈。
栈的进出顺序判断:
栈的基本操作:
顺序栈
顺序栈利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时由于栈的操作的特殊性,还必须附设一个位置指针top来动态地指示栈顶元素的顺序栈中的位置。通常以top=0表示空栈。
顺序栈的基本运算:
链栈:采用链表作为存储结构实现的栈。为便于操作,采用带头结点的单链表实现栈。因为栈的插入和删除操作仅限制在表头位置进行,所以链表的表头指针就作为栈顶指针。
顺序栈和链式栈的比较:实现链式栈和顺序栈的操作都是需要常数时间,即时间复杂度为O(1),主要从空间和时间两个方面考虑。初始时,顺序堆栈必须说明一个固定的长度,当堆栈不够满时,造成一些空间的浪费,而链式堆栈的长度可变则使长度不需要预先设定,相对比较节省空间,但是在每个节点中设置了一个指针域,从而产生了结构开销。
队列的概念
队列是一种先进先出的线性表,它只允许在表的一端进行插入,而在另一端删除元素。在队列中,允许插入数据一端成为队尾(rear),允许删除的那一端称为队头(front)。
队列的基本操作:
顺序队列
顺序队列利用一组地址连续的存储单元依次存放自队首到队尾的数据元素,同时由于队的操作的特殊性,还必须附两个位置指针front和rear来动态地指示队首元素和队尾元素在顺序队列中的位置。通常front=rear表示空队列。
队列同堆栈一样也有上溢和下溢现象。以外,队列中还存在“假溢出”现象。所谓“假溢出”是指在入队和出队操作中,头尾指针不断增加而不减小或只减小而不增加,致使被删除元素的空间无法重新利用,最后造成队列中有空闲空间,但是不能够插入元素,也不能够删除元素的现象。
链式队列:采用链表作为存储结构实现的队列。为便于操作,采用带头结点的单链表实现队列。因为队列的插入和删除操作位置不同,所以链表需要设置表头指针和表尾指针分别指队首和队尾。
循环队列
假设向量的空间是m,只要在入出队列时,将队首和队尾的指针对m做求模运算即可实现队首和队尾指针的循环,即队首和队尾指针的取值范围是0到m-1之间。
判断队空和队满的方法
四、数组和广义表
数组的定义
数组是我们熟悉的数据类型,数组中各元素具有统一的类型,并且数组元素的下标一般具有固定的上界和下界,因此,数组的处理比其它复杂的结构更为简单。
任何数组A都可以看作一个线性表。数组维数确定后,数据元素个数和元素之间的关系不再发生改变,适合顺序存储。
数组的基本操作
数组的顺序表示和实现
行优先顺序
列优先顺序
矩阵的压缩存储
有些特殊矩阵,非零元素呈某种规律分布或者矩阵中出现大量的零元素的情况下,会占用许多单元去存储重复的非零元素或零元素,这对高阶矩阵会造成极大的浪费,为了节省存储空间,对这类矩阵进行压缩存储——即为多个相同的非零元素只分配一个存储空间;对零元素不分配空间。
特殊矩阵:所谓特殊矩阵是指非零元素或零元素的分布有一定规律的矩阵,如对称矩阵、三角矩阵、对角矩阵等等。
对称矩阵
对称矩阵中元素关于主对角线对称,故只要存储矩阵中上三角或下三角中的元素,让每两个对称的元素共享一个存储空间,这样能节约近一半的存储空间。
n2 个元素可以压缩到 n(n+1)/2个空间中。
三角矩阵
以主对角线划分,三角矩阵有上三角和下三角两种。上三角矩阵它的下三角中的元素均为常数。下三角矩阵正好相反,它的主对角线上方均为常数。
稀疏矩阵
除了记录非零元素的值之外,还必须同时几下它所在的行和列的位置。稀疏矩阵的存储方法一般有三种:三元组法、行逻辑连接顺序表和十字链表法。
广义表
是由零个或多个原子或子表组成的优先序列,是线性表的推广。
广义表的存储结构
广义表中的数据元素可以具有不同的结构,因此,难以用顺序存储结构表示,通常采用链式存储结构,每个数据元素可用一个节点表示。由于广义表中有两种数据元素,因此需要有两种结构的节点——一种是表结点,一种是原子结点。
表结点由三个域组成:标志域、指示表头的指针的指针域和指示表尾的指针域;而原子域只需两个域:标志域和值域。
表结点由三个域组成:标志域、指示表头的指针域和指向下一个元素的指针;原子结点的三个域为:标志域、值域和指向下一个元素的指针。
五、树
树的定义
树的逻辑表示:树形表示法、文氏图表示法、凹入表示法、括号表示法。
结点:表示树中的元素,包括数据项及若干指向其子树的分支。
结点的度:结点拥有的子树树;树的度:一棵树中最大的结点度数
叶子结点:度为0的结点;分支结点:度不为0的结点;孩子:结点子树的根称为该结点的孩子;双亲:孩子结点的上层结点叫该结点的双亲;兄弟:同一双亲的孩子。
深度:树中结点的最大层次数。
有序树:树中各结点的子树从左至右是有次序的,不能互换。否则称为无序树。
树的性质
树中的结点数等于所有结点的度数加1。
度为m的树中第i层上至多有mi-1 个结点(i>=1)。
二叉树的概念
满二叉树:定义——一棵深度为k且具有2k-1个结点的二叉树。特点——每一层上的结点数都是最大结点数。
完全二叉树:定义——深度为k,有n个结点的二叉树当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时,称为完全二叉树。
二叉树的性质
二叉树的第i层上至多有2i-1(i>=1)个结点。
深度为K的二叉树至多有2k-1个结点。
对任何一棵二叉树T,如果其叶子数为n0,度为2的结点数为n2,则n0=n2+1。
一个有n个结点的完全二叉树,编号最大的分支结点的编号是
一个有n个结点的完全二叉树,n0的个数是不小于n/2的最小整数。
二叉树的存储结构
用一组连续的存储单元存储二叉树的数据元素。在存储之前对二叉树的所有结点安排一个适当的序列,使得结点在这个序列中的相互位置能反应出结点之间的逻辑关系。
特点:结点间关系蕴含在其存储位置中;浪费空间,适于存满二叉树和完全二叉树。
二叉树的链式存储结构
用一个链表来存储一棵二叉树,二叉树中每一个结点用链表中的一个链结点来存储。
遍历二叉树
遍历方法:
利用遍历结果确定二叉树:
先序遍历算法:
//先序遍历递归算法
void preorder(Tree t){
if(t==null)
return;
visit(t);
preorder(t.left());
preorder(t.right());
}
//先序遍历非递归算法
void PreOrderUnrec(Bitree t)
{
//创建栈来存放树的结点
SqStack s;
StackInit(s);
p=t;
while (p!=null || !StackEmpty(s))
{
while (p!=null) //遍历左子树
{
visite(p->data);
push(s,p);
p=p->lchild;
}//endwhile
if (!StackEmpty(s)) //通过下一次循环中的内嵌while实现右子树遍历
{
p=pop(s);
p=p->rchild;
}//endif
}//endwhile
}//PreOrderUnrec
中序遍历算法:
//中序遍历递归算法
void inorder(Tree t){
if(t==null)
return;
inorder(t.left());
visit(t);
inorder(t.right());
}
void InOrderUnrec(Bitree t)
{
//创建栈来存放树的结点
SqStack s;
StackInit(s);
p=t;
while (p!=null || !StackEmpty(s))
{
while (p!=null) //遍历左子树
{
push(s,p);
p=p->lchild;
}//endwhile
if (!StackEmpty(s)) //通过下一次循环中的内嵌while实现右子树遍历
{
p=pop(s);
visite(p->data); //访问根节点
p=p->rchild;
}//endif
}//endwhile
}//InOrderUnrec
后序遍历算法:
后序遍历递归算法
void inorder(Tree t){
if(t==null)
return;
inorder(t.left());
inorder(t.right());
visit(t);
}
//后序遍历非递归算法
对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问,因此其右孩子还为被访问。所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问。这样就保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是否是第一次出现在栈顶。
void postOrder2(BinTree *root) //非递归后序遍历
{
stack<BTNode*> s;
BinTree *p=root;
BTNode *temp;
while(p!=NULL||!s.empty())
{
while(p!=NULL) //沿左子树一直往下搜索,直至出现没有左子树的结点
{
BTNode *btn=(BTNode *)malloc(sizeof(BTNode));
btn->btnode=p;
btn->isFirst=true;
s.push(btn);
p=p->lchild;
}
if(!s.empty())
{
temp=s.top();
s.pop();
if(temp->isFirst==true) //表示是第一次出现在栈顶
{
temp->isFirst=false;
s.push(temp);
p=temp->btnode->rchild;
}
else //第二次出现在栈顶
{
cout<<temp->btnode->data<<" ";
p=NULL;
}
}
}
}
线索二叉树
利用二叉链表的空指针域,建立指向该结点的前驱/后继结点的指针,方便二叉树的线性化使用。
对二叉链表以某种次序遍历使其变为线索二叉树的过程叫做线索化。有先序线索二叉树、中序线索二叉树(更实用)和后序线索二叉树三种。
建立中序线索二叉树
树的存储结构
双亲表示法:用一组连续空间存储树的结点,每个节点包含两个域——数据域用来存放结点本身信息,双亲域用来指示本结点的双亲结点在数组中位置。
孩子表示法:采用孩子链表,每个结点的孩子结点用单链表存储,再用含n个元素的结构数组指向每个孩子链表。
带双亲的孩子链表
孩子兄弟表示法:用二叉链表作为树的存储结构。链表中结点的两个链域分为指向该结点的第一个孩子结点和下一个兄弟结点。
森林与二叉树的转换
将树转换为二叉树
将二叉树转换为树:
森林转换成二叉树:
二叉树转换成森林
哈夫曼树
结点的权及带权路径长度:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根节点到该结点之间的路径长度与该结点的权的乘积。
在一棵二叉树中,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称哈夫曼树。
六、图
图的概念
图是一种较线性表和树更为复杂的数据结构,在图形结构中,结点之间关系可以是任意的,图中任意两个数据元素之间都可能相关。
有向图和无向图
若无向图中的每两个顶点之间都存在着一条边,则称该无向图称作完全无向图;显然完全无向图中包含着e=n(n-1)/2条边。若有无向图中的每两个顶点之间都存在方向相反的两条边,则称该有向图称作完全有向图;显然完全有向图中包含有e=n(n-1)条边。
与图的边或弧相关的数叫做权,带权的图称为网。
对于有向图而言,度又分为出度和入度。顶点的出度——以顶点v为弧尾的弧的数目;顶点的入度——以顶点v为弧头的弧的数目;顶点的度为该顶点的出度和入度的和。
在无向图G中,如果从顶点v到顶点w存在路径,则称v到w是连通的。若图G中任意两个顶点之间都有路径相通,则称为连通图。
若无向图为非连通图,则图中各个极大连通子图称作此图的连通分量。任何连通图的连通分量只有一个,即本身,而非连通图则有多个连通分量。
在有向图中,若任意两个顶点之间都存在一条有向路径,则称此有向图为强连通图。
若有向图为非强连通图,其各个强连通子图称为它的强连通分量。
图的存储结构——邻接矩阵
邻接矩阵是表示顶点之间相邻关系的矩阵。
无向图的邻接矩阵:
有向图的邻接矩阵:
网的邻接矩阵:
图的存储结构——邻接表
邻接表存储方法是一种顺序分配与链式分配相结合的存储方法。它包括两部分:一部分是单链表,用来存放边的信息;另一部分是数组,主要用来存放顶点本身的数据信息。
无向图邻接表:
有向图的邻接表:
图的存储结构——十字链表
十字链表是有向图的另一种链式存储结构。可以看成是将有向图的邻接表和逆邻接表结合起来得到的一种链表。在十字链表中,每条弧和每个顶点分别对应着一个结点。
图的存储结构——邻接多重表
邻接多重表是无向图的另一种链式存储结构。邻接多重表和十字链表一样,每条边和每个顶点分别对应着一个结点。
图的遍历
从图中某个顶点出发游历图,访遍图中其余顶点,并且使图中的每个顶点仅被访问一次的过程
根据搜索方法的不同,图的遍历有两种:深度优先搜索(DFS)和广度优先搜索(WFS)。
深度优先搜索
连通图深度优先搜索的算法:
广度优先搜索
广度优先搜索是一种分层的搜索过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有回退的情况。因此,广度优先搜索不是一个递归的过程,其算法也不是递归的。
连通图的广度优先搜索算法:
非连通图广度优先搜索:
无向图的连通分量和生成树
连通图生成树:一个连通图的生成树是一个极小连通子图,它含有图中全部顶点,但只有构成一棵树的n-1条边。
当无向图为非连通图时,从图中某一顶点除法,利用深度优先搜索算法或广度优先搜索算法不可能遍历到图中的所有顶点,只能访问到该顶点所在的极大连通子图的所有顶点,该极大连通子图称为无向图的一个连通分量。
使用不同的遍历图的方法,可以得到不同的生成树;从不同的顶点出发,也可能得到不同的生成树。
最小生成树
在连通网的众多生成树中,各边权值之和最小的生成树称为最小代价生成树,简称最小生成树。
生成最小生成树算法——普里姆算法:
生成最小生成树算法——克鲁斯卡尔算法:
有向无环图
一个无环的有向图称为有向无环图,简称DAG图。
拓扑排序
在一个有向图中,若用顶点表示活动,有向边表示活动间的先后关系,称该有向图叫做顶点表示活动的网络,简称AOV网。
按照有向图给出的次序关系,将图中顶点排成一个线性序列,对于有向图中没有限定次序关系的顶点,则可以人为加上任意的次序关系。由此所得顶点的线性序列成为拓扑有序序列。
拓扑排序:由某个集合上的一个偏序(集合中仅有部分成员之间可以比较)得到该集合上的一个全序(集合中全体成员之间均可以比较)的操作称为拓扑排序。
拓扑排序的算法:
关键路径
在一个有向图中,若用顶点表示事件,弧表示活动,权表示活动持续时间,称该有向图叫做边表示活动的网络,简称为AOE网。
七、查找
概述
查找表:由同一类型的数据元素(或记录)构成的集合。
静态查找表
静态查找是指在静态查找表上进行的查找操作,在查找表中满足条件的数据元素的存储位置或各种属性。静态查找表的查找算法主要有:
顺序查找:从表的一端开始,顺序扫描线性表,依次将扫描到的关键字和给定值k进行比较,若当前扫描到的关键字与k相等,则查找成功;若扫描结束后,仍未找到关键字等于k的记录,则查找失败。
折半查找:对给定值k,逐步确定待查记录所在区间,每次将搜索空间减少一半,直到查找成功或失败为止。
分块查找:
动态查找表:表结构在查找过程中动态生成的这样一种查找表。实现动态查找方法:二叉排序树、平衡二叉树、B-树和B+树。
二叉排序树
定义:左子树的所有结点均小于根的值;右子树的所有节点均大于根的值;它的左右子树也分别为二叉排序树。
二叉排序树插入新结点的过程:
二叉排序树插入新节点递归算法:
二叉排序树删除结点的算法:
二叉排序树查找算法分析:
平衡二叉树
平衡二叉树又称为AVL树,设二叉树中结点的左子树和右子树的深度分别为HL和HR。
若在构造二叉排序树的同时,使其始终保持为AVL树,则此时的二叉排序树为平衡的二叉排序树。将一棵非平衡二叉排序树调整成平衡二叉排序树的“旋转”,分为:LL平衡旋转、RR平衡旋转、LR平衡旋转、RL平衡旋转。
B-树:
http://haicang.blog.51cto.com/2590303/1134864
B-树又称基本B树或多路平衡查找树。它是构造大型文件系统索引结构的一种数据结构类型,适合在磁盘等直接存取设备上组织动态的查找表。
该公式包括没有任何关键字的叶子结点。
B-树的查找算法思路:
B-树的查找效率取决于以下两个因素:包含k的结点在B-树种所在的层数h;结点中ki的个数n。
- B+树是B-树的变形,目的在于有效地组织文件的索引结构。
- m阶B+树与B-树的差异:
-
- B+树种可以有两种查找方式:顺序查找——类似单链表的查找,直接在数据层进行查找。随机查找——类似B-树的查找,不同的是搜索到索引层的key与k相等时,还得继续搜索下去,直到数据层为止。
-
- 哈希表
-
- 哈希表,根据设定的哈希函数H(key)和处理冲突的方法将一组关键字key映射到一个有限的连续的地址集上,并以关键字key在地址集中的“像”作为记录在表中的存储位置,这种表便称为哈希表,这一映射过程称为哈希造表或散列,所得存储位置称哈希地址或散列地址。
- 将不同的关键码映射到同一个哈希地址上,该现象称为冲突。
- 哈希函数的构造方法
- 常用的哈希函数构造方法有:直接定址法、除留余数法、乘余取整法、数字分析法、平方取中法、折叠法、随机数法。
- 直接定址法:
- 除留余数法:
- 乘余取整法:
- 数字分析法:
- 平方取中法:
- 叠加法:
- 随机数法:
- 处理冲突的方法
- 开放定址法、链地址法、再哈希法、建立一个公共溢出区
- 开放定址法:
- 链地址法:
- 再哈希法:
- 建立一个公共溢出区:
- 红黑树
- 红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。
- 红黑树虽然本质上是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。红黑树和AVL树的区别在于它使用颜色来标识结点的高度,它所追求的是局部平衡而不是AVL树中的非常严格的平衡。
- 红黑树的五个性质保证了红黑树的高度始终保持在logn:
- 红黑树的旋转操作:红黑树的旋转操作和AVL树一样,分为LL、RR、LR、RL四种旋转类型,差别在于旋转完成后改变的是结点的颜色,而不是平衡因子。
- 红黑树的插入和删除:http://blog.csdn.net/eric491179912/article/details/6179908
八、排序
- 排序概述
- 排序的分类:内部排序和外部排序(若待排序记录都在内存中,称为内部排序;若待排序记录一部分在内存,一部分在外存,则称为外部排序)。稳定排序和不稳定排序。
- 内部排序的算法:插入排序(希尔排序)、交换排序(快速排序)、选择排序(堆排序)、归并排序、基数排序。
- 插入排序
- 思想:每次将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象的适当位置上,直到对象全部插入为止。
- 具体实现算法:直接插入排序、折半插入排序、希尔排序
- 直接插入排序:
该算法的时间复杂度是:O(n2)void InsertSort(int a[]){
int i,j;
//按照有小到大的顺序排序
for(i=2;i<a.length;i++){
//找到无序表的第一个位置
if(a[i]<a[i-1]){
a[0]=a[i];
//将无序表依次向后移动
for(j=i-1;a[0]<a[j];j--){
a[j+1]=a[j];
}
//将数据插入相应位置
a[j+1]=a[0];
}
}
}
- 折半插入排序:
void BInsertSort(int a[]){
int i,j,high,low;
for(i=2;i<a.length;i++){
a[0]=a[i];
low=1;
high=i-1;
int min;
while(low<=high){ //使用折半查找到插入的位置
min=(high+low)/2;
if(a[0]<a[min])
high=min-1;
else
low=min+1;
}
for(j=i-1;j=>high+1;j++) //插入的位置是在high位置之后
a[j+1]=a[j];
a[high+1]=a[0];
}
}
- 希尔排序:
从排序过程可以看出,希尔排序的一个特点是:子序列的构成不是简单地“逐段分割”,而是将相隔某个“增量”的记录组成一个子序列。
void SheelSort(int a[],int dx){
//这是对直接插入排序的修改
//dx表示增量
//当j<=0时,插入位置已经找到
int i,j;
for(i=dx+1;i<a.length;i++){
if(a[i]<a[i-dx]){
a[0]=a[i];
for(j=i-dx;j>0&&a[0]<a[j];j-=dx)
a[j+dx]=a[j];
a[j+dx]=a[0];
}
}
}
- 交换排序
- 两两比较待排序记录的关键码,如果发生逆序(即排列顺序与排序后次序正好相反),则交换之,直到所有记录都排好序为止。
- 冒泡排序:
void bubbleSort(int a[]){
int i,j;
for(i=1;i<a.length-1;i++){
for(j=1;j<a.length-i;j++){
if(a[j]>a[j+1]){
a[0]=a[j];
a[j]=a[j+1];
a[j+1]=a[0];
}
}
}
}
- 快速排序:
快速排序平均时间复杂度和最好时间复杂度为:O(log2n),最坏时间复杂度为O(n2)。void Partition(int a[],int low,int high){
//这只是一趟快速排序的算法
a[0]=a[low];
while(low<high){
//从高位往低位扫描,找到数值小于关键字的位置,与low位置交换
while(low<high&&a[0]<=a[high])
high--;
a[low]=a[high];
//从低位往高位扫描,找到数值大于关键字的位置,与high位置交换
while(low<high&&a[low]<=a[0])
low++;
a[high]=a[low];
}
//最后将关键字放入数组中
a[low]=a[0];
}
- 选择排序
- 不断从待排记录序列中选出关键字最小的记录插入已排序记录序列的后面,直到n个记录全部插入已排序记录序列中。
- 简单选择排序:
- 堆排序:借助于完全二叉树结构进行的排序,是一种树形选择排序。其特点是——在排序过程中,将R[1...N]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。 堆的根节点是堆中元素值最小(或最大)的结点,称为堆顶顶点;从根节点到每个叶节点的路径上,元素的排序序列都是递减(或递增)有序的。
- 建立一个堆排序的方法:
- 堆排序的过程:
- 堆排序算法实现:
void HeapAdjust(int *a,int i,int size) //调整堆
{
int lchild=2*i; //i的左孩子节点序号
int rchild=2*i+1; //i的右孩子节点序号
int max=i; //临时变量
if(i<=size/2) //如果i不是叶节点就不用进行调整
{
if(lchild<=size&&a[lchild]>a[max])
{
max=lchild;
}
if(rchild<=size&&a[rchild]>a[max])
{
max=rchild;
}
if(max!=i)
{
swap(a[i],a[max]);
}
}
}
void BuildHeap(int *a,int size) //建立堆
{
int i;
for(i=size/2;i>=1;i--) //非叶节点最大序号值为size/2
{
HeapAdjust(a,i,size);
}
}
void HeapSort(int *a,int size) //堆排序
{
int i;
BuildHeap(a,size);
for(i=size;i>=1;i--)
{
//cout<<a[1]<<" ";
swap(a[1],a[i]); //交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面
//BuildHeap(a,i-1); //将余下元素重新建立为大顶堆
HeapAdjust(a,1,i-1); //重新调整堆顶节点成为大顶堆
}
}
- 归并排序
- “归并”的含义是将两个或两个以上的有序表合并成一个新的有序表。
-
- 两个有序表的合并算法Merge():
- 算法分析:
- 基数排序
- 基数排序是一种借助多关键字排序的思想对单逻辑关键字进行排序的方法,即先将关键字分解成若干部分,然后通过对各部分关键字的分别排序,最终完成对全部记录的排序。
- 多关键字的排序:
- 链式基数排序:
- 排序算法总结
九、时间复杂度
- 该时间复杂度表达作用于递归过程。
- n/b表示下一回递归的数据量为n/b,a表示递归的次数为a,除了递归之外的时间复杂度为O(nd)。
网友评论