《数据结构与算法》复习
分析数据结构,从存储,提取,空间大小来分析;
一般衡量一个算法的标准是:
时间的利用率 空间的利用率
影响算法执行时间主要是它处理的数据量的大小。
(1) 查找一个数组的最小元素:
template < typename T >
T min (T arr[], int n)
{
int index = 0 ; // index为最小元素的位置
for( int i=1; i<n; i++)
if (arr[i]<arr[index]) index = i ;
return arr [ index ] ;
}
线性结构:数组、向量、串、表、栈和队列。
数组:
是类型相同的数据项的有限集合,数组的诸元素之间具
有顺序关系,随机存取。
缺陷:一个程序中,数组的大小必须在声明时指定,程
序执行过程中不能动态地改变数组的大小。函数的返回类型不能是数组,向函数传递一个要处理的数组时,不但要给出数组名而且要给出数组的长度或能够找到数组的最后一个值。
向量:把数组和它的长度及容量封装在一个类结构中。向量的插入和删除仅能在尾端快速地进行
缺陷:在向量的插入过程中,可能因为存储空间的
不足而多次重新分配存储,引起向量元素的大量
复制;即使在不需要扩大存储空间的情况下,插
入和删除操作也可能移动元素,最坏的情况是新
元素被插在第一个元素之前,向量中所有元素都
要移动,运行效率很低。
链表:每个结点中除含有元素本身的值外,还包含反映表中元素之间逻辑顺序的指针next,头指针head;
循环链表:链表仅有一个尾指针tail,由tail可以直接访问第一个和最后一个结点。对双向循环链表, curr = curr->next->prev = curr->prev->next
栈:只能在序列的一端存取元素。允许存取的一端称为栈顶(top),另一端称为栈底(bottom),后进先出。
队列中,最先入队的元素处于队头位置,删除时,只能删除队头元素,先进先出。向量的插入和删除仅能在尾端快速地进行,而deque可以在两端快速地进行插入和删除。
栈的应用举例---算式表达式的求值
1.中缀表达式 (a+b)*c,对应的后缀表达式是ab+c*
2.中缀表达式g-(a+b)*((c-d)*e+f)对应的后缀表达式是gab+cd–e*f+*–
可以看出,后缀表达式中操作数的顺序与中缀表达
式中操作数的顺序完全相同,而操作符的顺序就是实际
计算的顺序。
计算后缀表达式的规则是从左到右地扫描后缀表达
式,当遇到一个操作符时,用该操作符对它前面的两个
操作数进行相应的运算,并用运算的结果代替由这两个
操作数和操作符组成的子串。在上面的例子中,用a+b
的和代替 “ab+”,c–d的差代替“cd–”。然后再用(c–d)*e
的结果代替由c–d的差与“e*”组成的子串,……,等等。
最后,用g–(a+b)*((c–d)*e+f)的结果代替整个串。
实现方法:
设置一个操作数栈,在扫描过程中,当遇到操作数
时,将它放入栈中,遇到操作符时,从栈中弹出两个操
作数,用该操作符对这两个操作数进行相应的运算,并
将运算结果作为一个操作数放入栈中。
异常类Expression_error用来检查表达式可能出现
的错误,产生Expression_error异常。
class Expression_error :public exception // 异常类
{
public:
Expression_error(const string& s):exception(s.c_str()){}
};
定义类PostfixExp_Eval计算后缀表达式
class PostfixExp_Eval
{
public:
PostfixExp_Eval(const string& str=NULL):
postfixExp (str){ };
double Evaluate();
private:
string postfixExp ; // 后缀表达式
stack<double>opndStack; // 操作数栈opndStack
bool get2opnds(double& left, double& right);
bool isOperator(char& c) const;
double doOperator(char op);
} ;
bool PostfixExp_Eval::get2opnds(double& left, double& right)
{
if(!opndStack.empty()) //如果栈不空
{
right=opndStack.top(); //取栈顶操作数作为右操作数
opndStack.pop();
}
if(!opndStack.empty()) //如果栈不空
{
left=opndStack.top(); //取栈顶操作数作为左操作数
opndStack.pop();
}
else
throw Expression_error ("缺少操作数!");
return true;
}
bool PostfixExp_Eval::isOperator(char& c) const
{
char* operators = "+-*/^";
if(strchr(operators , c)==NULL) return false ;
else return true ;
}
double PostfixExp_Eval::doOperator( char op )
{ double x , y , z ;
get2opnds(x, y) ; // x、y是从操作数栈取出的两个操作数
switch (op)
{ case '+‘ : z=x+y; break ;
case '–‘ : z=x-y; break ;
case '*‘ : z=x*y; break ;
case '/‘ :
{
if ( y==0.0 ) throw Expression_error("Expression_error:除数为0!");
z=x/y ; break ;
}
case '^':
if(x==0 && y==0)
{ throw Expression_error("Expression_error: 0^0无定义"); }
z = 1;
while(y>0)
{ z*=x; y--; }
break ;
}
return z ;
}
1、操作数在后缀表达式中的顺序与在中缀表达式中相同,因
此,从左到右扫描中缀表达式的字符串,遇到操作数时,直接把
它送到输出串。
2、在后缀表达式中操作符的顺序就是实际计算的顺序,在扫描
中缀表达式的过程中,遇到操作符时不能立即输出,必须将它与
后面的操作符进行优先级比较,输出优先级较高的操作符。因此
需用一个栈来存储暂时还不能输出的操作符。如果当前扫描到的
操作符的优先级高于处于栈顶的操作符的优先级,则将它推入栈
中,否则,依次从栈顶弹出那些优先级比当前操作符优先级高或
相等的操作符,并送到输出串,直至栈顶出现一个优先级较低的
操作符,然后再将当前扫描到的操作符推入栈中。
3、遇到左括号‘(’和对应的右括号‘)’时的处理。
4、输入串扫描结束,若栈中尚有未输出的运算符,则逐个将它
们弹出并送到输出串中。
-----------
非线性结构:树和图
树:根(root),每个集合Ti又是一棵树。
与树有关的一些术语:
双亲(parent),子女(child),兄弟(sibling),
后代(descendant) ,祖先(ancestor) ,结点的度
(degree) ,叶(leaf)结点 ,终端结点或外结点。分
支结点或内结点。树的度 ,边(adge) ,路径
(path)与路径长度( path length) ,树的高度
(height) ,有序树(ordered tree) ,森林(forest)
二叉树:每个结点至多只有两棵子树
如果规定总是先遍历左子树再遍历右子树,则有VLR、
LVR和LRV三种顺序,分别称为先序、中序和后序遍
历。
中序遍历的非递归算法:
由中序遍历的定义,在访问根结点之前,先
遍历它的左子树,为了在遍历左子树之后能够回
到根结点,在由根向下进入左子树之前应将根结
点的位置保存起来。由于二叉树结构是递归的,
对每棵子树的遍历都应同样处理,所以需要用一
个栈来保存自根向下的过程中遇到的每一个结
点。当一个结点的左子树被遍历后,从栈顶弹出
该结点访问,然后由该结点进入它的右子树,开
始对一棵新的子树进行中序遍历。
中序遍历的非递归算法(设二叉树的根结点的指针是t):
step1.[start]
makenull(stk) ; // 建立一个空栈 stk
if ( t!=0) p=t ; else goto step5 ;
step2.[decend left]
while(p!=0)
{ stk.push(p) ; p=p->left ; }
step3.[visit root]
p=stk.top() ; stk.pop() ; cout<<p->data ;
step4.[decend right]
p=p->right ;
if(p!=0) goto step2 ; // 转step2,对p所指的子树进行遍历
if(! stk.empty()) goto step3 ; // 如果子树为空,栈非空,转step3
step5.[algorithm termination]
template <typename T> // 非递归的中序遍历
void INORDER( Binarytnode<T>* t )
{
stack<Binarytnode<T>*> stk ; // 栈, 存储结点的地址
Binarytnode<T>* p = t ;
while ( p || !stk.empty() )
{
if ( p ) // p向左下方移动
{
stk.push(p) ; // 结点入栈
p = p->Left() ; // 向左走
}
else
{
p = stk.top() ; // 取栈顶结点
stk.pop();
cout<<p->data ; // 访问结点
p = p->Right() ; // 向右走
}
} // while
}
对树
进行先序、中序和后序遍历的结果分别是:
先序序列:A B D E I C F G H
中序序列:D B I E A F C G H
后序序列:D I E B F G H C A
排序:
快速排序:基本思想是选取某个元素作为基准(pivot),
将被排序序列划分成左右两个子序列,使得左边
子序列中每个元素都小于或等于基准值,右边子
序列中每个元素都大于或等于基准值;然后用同
样的方法递归地处理这两个子序列,直至完成全
部元素的排序
template <typename Raniterator, typename T >
Raniterator Partition ( Raniterator p , Raniterator q , T pivot )
{ // 对 [p,q) 进行划分,pivor作为基准
while(1)
{
while( *p<pivot ) ++p ; // 从左向右,小的留在左边
--q ;
while( pivot<*q ) --q ; // 从右向左,大的留在右边
if ( !(p<q) ) return p ; // 划分完成 返回
swap ( *p, *q ) ; // 交换
++p ;
}
}
template< typename T> void quick_sort ( T* first , T* last )
{
if ( last – first > 1 ) // 如果 [first,last) 有多于1个元素
{
T elem = *( first + ( last – first )/2 ) ; // elem作为基准
T* split = Partition( first , last , elem ) ;
if ( (split - first) <= last - split ) // 对两个子序列排序
{ // [first,split) 较短
quick_sort ( first , split ); // 递归,排序[first,split)
quick_sort ( split , last ) ; // 递归,排序[split,last)
}
else
{ // [split,last) 较短
quick_sort ( split , last ) ; // 递归,排序[split,last)
quick_sort ( first , split ) ; // 递归,排序[first,split)
}
}
}
图:
G由集合V和集合E组成,记为G=(V,E),其中,V是顶点(vertex)的有限集合,E是边(edge)的有限集合。
术语:无向图,有向图,终点,入度和出度,路径长度。
(1)深度优先搜索
对图进行深度优先搜索的过程是从图中一个选定的顶点x出
发,访问x,然后对邻接于x的某个顶点y,若y未被访问,则从y
开始继续进行深度优先搜索。当能由y到达的所有顶点都已被访
问,回到x,若仍有邻接于x且未被访问过的顶点,由该顶点出发
继续搜索其他路路径, ……,当能由x到达的所有顶点都已被访
问,则由x出发的一次搜索过程结束。在搜索过程中,每到达一
个顶点,总是尽可能地向深度前进,只有在不能继续向前或与之
相邻接的顶点都已被访问,才退回到前一个被访问的顶点,从该
顶点继续向前搜索。
广度优先搜索与树的层次遍历类似。假设图G的所
有顶点都未被访问,从G的一个顶点x出发对G进行一
次广度优先搜索,先访问x,然后访问所有邻接于x的
顶点x1,x2,…,xt;再依次访问邻接于x1的所有顶
点,邻接于x2的所有顶点,……,邻接于xt的所有顶
点;依此类推,直至由x能够到达的所有顶点都已被访
问,从x开始的一次搜索过程结束。这种搜索的特点是
尽可能在横的方向上进行搜索,所以称为广度优先搜
索。
http://www.cnblogs.com/lscheng/archive/2013/09/11/3313947.html