第一讲 基本概念
1.1 什么是数据结构
1.数据结构是数据对象,及存在于该对象的实例和组成实例的数据元素之间的各种联系,联系可以通过定义相关函数来给出,数据结构是ADT的物理实现。
数据对象在计算机中的组织方式包括逻辑结构和物理存储结构,数据对象必定与一系列加在其上的操作相关联,完成操作所用的方法就是算法。
1.2什么是算法
1.有限指令集,接受一些输入,产生输出,有限步骤后终止,每条指令必须有充分明确的目标,不可以有歧义,计算机能处理的范围之内,描述不依赖于任何计算机语言及具体实现手段。
2.for循环的时间复杂度等于循环次数乘以循环体代码的复杂度
if-else结构复杂度取决于if条件判断复杂度和两个分歧部分的复杂度,总体复杂度取三者最大。
1.3最大数例子
int Max3( int A, int B, int C )
{ /* 返回3个整数中的最大值 */
return A > B ? A > C ? A : C : B > C ? B : C;
}
int DivideAndConquer( int List[], int left, int right )
{ /* 分治法求List[left]到List[right]的最大子列和 */
int MaxLeftSum, MaxRightSum; /* 存放左右子问题的解 */
int MaxLeftBorderSum, MaxRightBorderSum; /*存放跨分界线的结果*/
int LeftBorderSum, RightBorderSum;
int center, i;
if( left == right ) { /* 递归的终止条件,子列只有1个数字 */
if( List[left] > 0 ) return List[left];
else return 0;
}
/* 下面是"分"的过程 */
center = ( left + right ) / 2; /* 找到中分点 */
/* 递归求得两边子列的最大和 */
MaxLeftSum = DivideAndConquer( List, left, center );
MaxRightSum = DivideAndConquer( List, center+1, right );
/* 下面求跨分界线的最大子列和 */
MaxLeftBorderSum = 0; LeftBorderSum = 0;
for( i=center; i>=left; i-- ) { /* 从中线向左扫描 */
LeftBorderSum += List[i];
if( LeftBorderSum > MaxLeftBorderSum )
MaxLeftBorderSum = LeftBorderSum;
} /* 左边扫描结束 */
MaxRightBorderSum = 0; RightBorderSum = 0;
for( i=center+1; i<=right; i++ ) { /* 从中线向右扫描 */
RightBorderSum += List[i];
if( RightBorderSum > MaxRightBorderSum )
MaxRightBorderSum = RightBorderSum;
} /* 右边扫描结束 */
/* 下面返回"治"的结果 */
return Max3( MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum );
}
int MaxSubseqSum3( int List[], int N )
{ /* 保持与前2种算法相同的函数接口 */
return DivideAndConquer( List, 0, N-1 );
}
第二讲 线性结构
2.1线性表及其实现
1.第一种方法表示多项式的方法,数组各分量对应多项式各项;第二种方法用二元数组分量表示系数,指数组成的结构,对应非零项;第三种方法用链表结构存储非零项,每个结点包括系数和指数两个数据域,以及一个指针域。
2.线性表的操作:初始化一个空线性表,根据位序,返回相应元素,在线性表中查找元素第一次出现的位置,在位序i前插入一个新元素,删除指定位序的元素,返回线性表的长度。
3.利用数组的存储空间顺序存储线性表元素
访问下标为i的元素:L.Data[i]或PtrL->Data[i]
线性表的长度:L.Last+1或者PtrL->Last+1.
4.初始化
List MakeEmpty()
{List PtrL;
Ptrl=(List)malloc(sizeof(struct LNode));
PtrL->Last=-1;
return PtrL;}
int Find(ElementType,List PtrL)
{int i=0;
while(i<=Ptrl->Last&&Ptrl->Data[i]!=X)
i++;
if(i>PtrL->Last)return -1;
else return i;}
5.插入
void Insert(ElementType X, int i,List PtrL)
{int j;
if(PtrL->Last==MAXSIZE-1){
printf("表满")}
return;}
if(i<1||i>PtrL->Last+2){
printf(位置不合法);}
for(j=PtrL->Last;j>=i-1;j--)
PtrL->Data[j+1]=PtrL->Data[j];
PtrL->Data[i-1]=X;
PtrL->Last++;
return;
6.删除
void Delete(in i,List PtrL)
{
int j;
if(i<1||i>PtrL->Last+1){
printf("不存在第%d个元素",
return;)}
for(j=i;j<=PtrL->Last;j++){
PtrL->Data[j-1]=PtrL->Data[j];
PtrL->Last--;
return;}}
7.线性表的链式存储实现
typedef struct LNode *List;
struct LNode{
ElementType Data;
List Next;};
struct Lnode L;
List PtrL;
8.求表长
int Length(List PtrL){
List p=PtrL;
int j=0;
while(p){
p=p->Next;
j++;}
return j;}
9.查找
按序号查找FindKth;
List FindKth(int K,List PtrL){
List p=PtrL;
int i=1;
while(p!=NULL&&i<K){
p=p->Next;
i++;}
if(i==K)return p;
elase return NULL;}
按值查找
List Find(ElementType X,List PtrL)
{List p=PtrL;
while(p!=NULL&&p->Data!=X)
p=p->Next;
return p;}
10.线性表操作
typedef int Position;
typedef struct LNode *List;
struct LNode {
ElementType Data[MAXSIZE];
Position Last;
};
/* 初始化 */
List MakeEmpty()
{
List L;
L = (List)malloc(sizeof(struct LNode));
L->Last = -1;
return L;
}
/* 查找 */
#define ERROR -1
Position Find( List L, ElementType X )
{
Position i = 0;
while( i <= L->Last && L->Data[i]!= X )
i++;
if ( i > L->Last ) return ERROR; /* 如果没找到,返回错误信息 */
else return i; /* 找到后返回的是存储位置 */
}
/* 插入 */
/*注意:在插入位置参数P上与课程视频有所不同,课程视频中i是序列位序(从1开始),这里P是存储下标位置(从0开始),两者差1*/
bool Insert( List L, ElementType X, Position P )
{ /* 在L的指定位置P前插入一个新元素X */
Position i;
if ( L->Last == MAXSIZE-1) {
/* 表空间已满,不能插入 */
printf("表满");
return false;
}
if ( P<0 || P>L->Last+1 ) { /* 检查插入位置的合法性 */
printf("位置不合法");
return false;
}
for( i=L->Last; i>=P; i-- )
L->Data[i+1] = L->Data[i]; /* 将位置P及以后的元素顺序向后移动 */
L->Data[P] = X; /* 新元素插入 */
L->Last++; /* Last仍指向最后元素 */
return true;
}
/* 删除 */
/*注意:在删除位置参数P上与课程视频有所不同,课程视频中i是序列位序(从1开始),这里P是存储下标位置(从0开始),两者差1*/
bool Delete( List L, Position P )
{ /* 从L中删除指定位置P的元素 */
Position i;
if( P<0 || P>L->Last ) { /* 检查空表及删除位置的合法性 */
printf("位置%d不存在元素", P );
return false;
}
for( i=P+1; i<=L->Last; i++ )
L->Data[i-1] = L->Data[i]; /* 将位置P+1及以后的元素顺序向前移动 */
L->Last--; /* Last仍指向最后元素 */
return true;
}
typedef struct LNode *PtrToLNode;
struct LNode {
ElementType Data;
PtrToLNode Next;
};
typedef PtrToLNode Position;
typedef PtrToLNode List;
/* 查找 */
#define ERROR NULL
Position Find( List L, ElementType X )
{
Position p = L; /* p指向L的第1个结点 */
while ( p && p->Data!=X )
p = p->Next;
/* 下列语句可以用 return p; 替换 */
if ( p )
return p;
else
return ERROR;
}
/* 带头结点的插入 /
/注意:在插入位置参数P上与课程视频有所不同,课程视频中i是序列位序(从1开始),这里P是链表结点指针,在P之前插入新结点 */
bool Insert( List L, ElementType X, Position P )
{ /* 这里默认L有头结点 */
Position tmp, pre;
/* 查找P的前一个结点 */
for ( pre=L; pre&&pre->Next!=P; pre=pre->Next ) ;
if ( pre==NULL ) { /* P所指的结点不在L中 */
printf("插入位置参数错误\n");
return false;
}
else { /* 找到了P的前一个结点pre */
/* 在P前插入新结点 */
tmp = (Position)malloc(sizeof(struct LNode)); /* 申请、填装结点 */
tmp->Data = X;
tmp->Next = P;
pre->Next = tmp;
return true;
}
}
/* 带头结点的删除 /
/注意:在删除位置参数P上与课程视频有所不同,课程视频中i是序列位序(从1开始),这里P是拟删除结点指针 */
bool Delete( List L, Position P )
{ /* 这里默认L有头结点 */
Position tmp, pre;
/* 查找P的前一个结点 */
for ( pre=L; pre&&pre->Next!=P; pre=pre->Next ) ;
if ( pre==NULL || P==NULL) { /* P所指的结点不在L中 */
printf("删除位置参数错误\n");
return false;
}
else { /* 找到了P的前一个结点pre */
/* 将P位置的结点删除 */
pre->Next = P->Next;
free(P);
return true;
}
}
11.线性表是由同类型数据元素构成的有序序列的线性结构。元素个数为线性表的长度。数据对象集,操作集
12.链表表示堆栈的时候,一定要用链表头做top。另外用链表入栈不需要检查空间足不足。
13.用链表处理中缀表达式变成后缀表达式的时候,先把运算符号入栈,然后当下一个符号优先级比上一个低的时候,上一个符号可以出栈了。
运算数:直接输出;左括号:压入堆栈;右括号:将栈顶的运算符弹出并输出,直到遇到左括号;运算符:若优先级大于栈顶运算符,则压栈,优先级小于栈顶,将栈顶运算符弹出并输出。
14.堆栈的应用:函数调用及递归,深度优先搜索,回溯算法。
15.堆栈用数组+top或者链表
-
typedef int Position;
struct SNode {
ElementType Data; / 存储元素的数组 /
Position Top; / 栈顶指针 /
int MaxSize; / 堆栈最大容量 */
};
typedef struct SNode *Stack;Stack CreateStack( int MaxSize )
{
Stack S = (Stack)malloc(sizeof(struct SNode));
S->Data = (ElementType *)malloc(MaxSize * sizeof(ElementType));
S->Top = -1;
S->MaxSize = MaxSize;
return S;
}bool IsFull( Stack S )
{
return (S->Top == S->MaxSize-1);
}bool Push( Stack S, ElementType X )
{
if ( IsFull(S) ) {
printf("堆栈满");
return false;
}
else {
S->Data[++(S->Top)] = X;
return true;
}
}bool IsEmpty( Stack S )
{
return (S->Top == -1);
}ElementType Pop( Stack S )
{
if ( IsEmpty(S) ) {
printf("堆栈空");
return ERROR; /* ERROR是ElementType的特殊值,标志错误 */
}
else
return ( S->Data[(S->Top)--] );
}
17.堆栈的操作
typedef struct SNode *PtrToSNode;
struct SNode {
ElementType Data;
PtrToSNode Next;
};
typedef PtrToSNode Stack;
Stack CreateStack( )
{ /* 构建一个堆栈的头结点,返回该结点指针 */
Stack S;
S = (Stack)malloc(sizeof(struct SNode));
S->Next = NULL;
return S;
}
bool IsEmpty ( Stack S )
{ /* 判断堆栈S是否为空,若是返回true;否则返回false */
return ( S->Next == NULL );
}
bool Push( Stack S, ElementType X )
{ /* 将元素X压入堆栈S */
PtrToSNode TmpCell;
TmpCell = (PtrToSNode)malloc(sizeof(struct SNode));
TmpCell->Data = X;
TmpCell->Next = S->Next;
S->Next = TmpCell;
return true;
}
ElementType Pop( Stack S )
{ /* 删除并返回堆栈S的栈顶元素 */
PtrToSNode FirstCell;
ElementType TopElem;
if( IsEmpty(S) ) {
printf("堆栈空");
return ERROR;
}
else {
FirstCell = S->Next;
TopElem = FirstCell->Data;
S->Next = FirstCell->Next;
free(FirstCell);
return TopElem;
}
}
2.3队列
1.生成队列,判别是否满,插入,判别队列是否空,删除并返回。
2.顺序存储用数组,或者链表存储。
front,rear,开始front,rear-1,删除front+1,插入rear-1.链表的末尾做插入没问题,做删除有问题
3.判断队列是否为空front=rear是空的,但顺环队列会出现问题,解决方法是用size或者tag标记插入输出,或者仅仅用n-1个数组空间。
2.5多项式的表示
1.数组编程容易,调试简单,需要事先确定数组的大小
2.链表动态性强,编程略为复杂,调试比较困难
3树
3.1树与树的表示
1.查找:根据某个给定的关键字K,从集合R中找出关键字与K相同的记录
静态查找:集合中记录是固定的,没有插入和删除操作,只有查找。动态查找:集合中记录是动态变化的,除查找外还可能发生插入和删除。
2.顺序查找:
有哨兵:
int SequentialSearch(List Tbl,ElementType K)
{int i;
Tbl->Element[0]=K;
for(i=Tbl->Length;Tbl->Element[i]!=K;i--);
return i;)}
typedef struct LNode *List;
struct LNode{
ElementType Element[MAXSIZE];
int Length;}
3.二分查找Binary Search
int BinarySearch(List Tbl,ElementType K)
{int left,right,mid,NoFound=-1;
left=1;
right=Tbl->Length;
while(left<=right)
{mid=(left+right)/2;
if(K<Tbl->Element[mid])
right=mid-1;
else if(K>Tbl->Element[mid])
left=mid+1;
else return mid;}
return NotFound;
}
3.n个结点判定树深度为[lgn+1]。树和非树:子树是不相交的,除了根结点外,每个结点有且只有一个父结点,一棵N个结点的树有N-1条边。
4.结点的度,结点的子树个数。树的度,树的结点中最大的度数。叶结点,度为0的结点。父结点,有子树的结点是其子树的根结点的父结点。子结点,若A是B的父结点,则B是A的子结点。祖先结点,子孙结点,结点的层次,树的深度。
5.斜二叉树,完美二叉树,完全二叉树。
6.一个二叉树第i层的最大结点数为2^(i-1),i>1.
深度为k的二叉树有最大的结点总数为 2^k-1,k>=1.
对任何非空二叉树T,若n0表示叶结点的个数,n2是度为2的非叶结点个数,那么两者满足关系n0=n2+1.
7.二叉树重要操作有:判别BT是否为空,遍历,按照某顺序访问每个结点,创建一个二叉树。先序,中序,后序,层次遍历。
8.顺序存储结构 ,二叉树。
完全二叉树可以用数组表示,非根结点(序号i>1)的父结点序号是[i/2]向下取整;
结点(序号为i)的左孩子结点是2i,若2i<=n,否则没有左孩子,结点的右孩子2i+1,若2i+1<N,则没有右孩子。
9.链表存储
typedef struct TreeNode *BinTree;
typedef BinTree Position;
struct TreeNode{
ElementType Data;
BinTree Left;
BinTree Right;}
3.3二叉树的遍历
1.先序遍历
根结点,先左后右
void PreOrderTraversal(BinTree BT)
{if(BT){
printf("%d",BT->Data);
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);}
}
2.中序遍历
void InOrderTraversal(BinTree BT)
{if(BT){
PreOrderTraversal(BT->Left);
printf("%d",BT->Data);
PreOrderTraversal(BT->Right);}
}
3.后序递归
void PostOrderTraversal(BinTree BT)
{if(BT){
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);
printf("%d",BT->Data);}
}
4.二叉树的非递归遍历
中序遍历非递归遍历算法
遇到结点,把它压栈,遍历左子树,左子树遍历结束后从栈顶弹出结点并访问它,然后按其右指针再去中序遍历该结点的右子树。
void InOrderTraversal(BinTree BT)
{BinTree T=BT;
Stack S=CreatStack(MaxSize);
while(T||!IsEmpty(S)){
while(T){
Push(S,T);
T=T->Left;}
if(!IsEmpty(S)){
T=Pop(S);
printf("%5d",T->Data);
T=T->Right;}}}
先序遍历
void InOrderTraversal(BinTree BT){
BinTree T BT;
Stack S=CreatStack(MaxSize);
while(T||!IsEmpty(S)){
while(T){
printf("%5d",T->Data);
Push(S,T);
T=T->Left;}
if(!IsEmpty(S)){
T=Pop(S);
T=T->Right;
}}}
层序遍历
二叉树遍历的核心问题:二维结构的线性化
队列实现:遍历从根结点开始,根结点入队,然后结点出队,访问结点,左右儿子入队。
void LevelOrderTraversal(BinTree BT)
{ Queue Q;BinTree T;
if(!BT)return;
Q=CreatQueue(MaxSize)
AddQ(Q,BT);
while(!IsEmptyQ(Q)){
T=DeleteQ(Q);
printf("%d\n",T->Data);
if(T->Left)AddQ(Q,T->Left);
if(T->Right)AddQ(Q,T->Right);
}}
遍历二叉树的应用:输出二叉树的叶子结点
在中序遍历中增加if(!BT-Left&&BT->Right)
求二叉树的高度:在后序遍历中
int PostOrderGetHeight(BinTree BT){
int HL,HR,MaxH;
if(BT){
HL=PostOrderGetHeight(BT->Left);
HR=PostOrderGetHeight(BT->Right);
MaxH=(HL>HR)?HL:HR;
return(MaxH+1);
}
else return 0;}
5.二元运算表达式树及其遍历
有两种遍历序列确定二叉树,必须有中序遍历。
根据先序遍历第一个结点确定根结点,根据根结点在中序遍历序列中分割出左右两个子序列,对左子树和右子树分别递归使用相同的方法继续分解。
3.4小白:树的同构
1.通过左右孩子互换变成一样就叫同构。
2.结构数组表示二叉树:静态链表
struct TreeNode
{ ElementType Element;
Tree Left;
Tree Right;
}T1[MaxTree],T2[MaxTree];
3.int main(){
Tree R1,R2;
R1=BuildTree(T1)
R2=BuildTree(T2);
if(Isomorphic(R1,R2))printf("Yes\n");
else printf("No\n");
return 0;}
创建二叉树
Tree BuildTree(struct TreeNode T){
scanf("%d\n",&N);
if(N){
for(i=0;i<N;i++)check[i]=0;
for(i=0;j<N;i++){
scanf("%c %c %c\n",&T[i].Element,&cl,&cr);
if(cl!='-'){
T[i].Left=cl='0';
check[T[i].Left]=1;}
else T[i].Left=Null;}
if(cr!='-'){
T[i].Right=cl='0';
check[T[i].Right]=1;}
else T[i].Right=Null;}
for(i=0;i<N;i++)
if(!check[i]break;)
Root=i;
}
return Root;
}
3.判别二叉树同构
int Isomorphic(Tree R1,Tree R2)
{
if((R1==Null)&&(R2==Null))
return 1;
if(((R1==Null)&&(R2!=Null))||((R1!=Null)&&(R2==Null)))
return 0;
if(T1[R1].Element !=T2[R2].Element)
return 0;
if((T1[R1].Left==Null)&&(T2[R2].Left==Null))
return Isomorphic(T1[R1].Right,T2[R2].Right);}
if(((T1[R1].Left!=Null)&&(T2[R2].Left!=Null))&&((T1[T1[R1].Left].Element)==(T2[T2[R2].Left].Element)))
return(Isomorphic(T1[R1].Left,T2[R2].Left)&&Isomorphic(T1[R1].Right,T2[R2]Right));
else
return(Isomorphic(T1[R1].Left,T2[R2].Right)&&Isomorphic(T1[R1].Right,T2[R2]Left));}
第四讲:树(中)
4.1二叉搜索树
1.查找问题:静态查找与动态查找
针对动态查找,数据如何组织?
2.非空左子树的所有键值小于其根结点的键值,非空右子树的所有键值大于其根结点的键值,左右子树都是二叉搜索树。
3.从二叉搜索树BST中查找元素X,返回其所在结点的地址;从二叉搜索树中查找并返回最小元素所在结点的地址,从二叉搜索树中查找并返回最大元素所在结点的地址。
4.二叉搜索树的查找操作:find
查找从根结点开始,树为空,返回null。非空,根结点关键字和x比较,x小于根结点键值,在左子树搜索,x大于根结点键值,在右子树中搜索,相等的话,搜索完成,返回指向此结点的指针。
Position Find(ElementType X,BinTree BST)
{
if(!BST)return NULL;
if(X>BST->Data)
return Find(X,BST->Right);
Else if(X<BST->Data)
return Find(X,BST->Left);
else
return BST;}
改进
Position IterFind(ElementType X,BinTree BST)
{while(BST){
if(X>BST->Data)
BST=BST->Right;
else if(X<BST->Data)
BST=BST->Left;
else
return BST;
}
return NULL;
}
找最小值
Position FindMin()
5.二叉搜索树的插入
BinTree Insert(ElementType X,BinTree BST)
{
if(!BST){
BST=malloc(sizeif(struct TreeNode));
BST->Data=X;
BST->Left=BST->Right=NULL;
}else
if(X<BST->Data)
BST->Left=Insert(X,BST->Left);
else if(X>BST->Data)
BST->Right=Insert(X,BST->Right);
return BST;}
6.二叉搜索树的删除
叶结点直接删除,修改父结点为null
只有一个孩子,将父结点指针指向要删除结点的孩子结点。
要删除的结点有左右子树,用另一个结点替代被删除的结点,右子树的最小元素或者左子树的最大元素。
BinTree Delete(ElementType X,BinTREE BST){
Position Tmp;
if(!BST)printf("要删除的元素未找到");
else if(X<BST->Data)
BST->Left=Delete(X,BST->Left);
else if(X>BST->Data)
BST->Right=Delete(X,BST->Right);
else
if(BST->Left && BST->Right)
{Tmp=FindMin(BST->Right);
BST->Data=Tmp->Data;
BST->Right=Delete(BST->Data,BST->Right);
}else{
Tmp=BST;
if(!BST->Left)
BST=BST->Right;
else if(!BST->Right)
BST=BST->Left;
free(Tmp);}
return BST;}
网友评论