python-数据结构与算法补充1

数据结构

  • 第二章
    • 2.9算法时间复杂度
      • 2.9.2推导大O阶方法
      • 2.9.5对数阶
    • 2.11最坏情况与平均情况
    • 2.12算法空间复杂度
  • 第三章 线性表
    • 3.4线性表的顺序存储结构
      • 3.4.3数据长度与线性表长度区别
    • 3.5顺序存储结构的插入与删除
    • 3.6线性表的链式存储结构
    • 3.8单链表的插入与删除
    • 3.13循环链表
    • 3.14双向链表
  • 第四章 栈与队列
  • 4.4栈的顺序存储结构及实现
    • 4.4.1栈的顺序存储结构
  • 4.5两栈共享空间
  • 4.6栈的链式结构及实现
    • 4.6.2栈的链式存储结构——进栈操作
    • 4.6.3栈的链式存储结构——出栈操作
  • 4.8栈的应用——递归
  • 4.10队列的定义
  • 4.12循环队列
  • 4.13队列的链式存储结构及实现
  • 4.13.1队列的链式存储结构-入队操作
  • 4.13.2队列的链式存储结构-入队操作
  • 第5章 串
    • 5.6朴素的模式匹配算法
    • 5.7KMP模式匹配算法-代码没懂
  • 第六章 树
    • 6.2树的定义
    • 6.4树的存储结构
      • 6.4.1双亲表示法
      • 6.4.2孩子表示法
      • 6.4.3孩子兄弟表示法
    • 6.5二叉树的定义
      • 6.5.2特殊二叉树
    • 6.6二叉树的性质
    • 6.7二叉树的存储结构
      • 6.7.2二叉链表
    • 6.8遍历二叉树
      • 6.8.3前序遍历二叉树
      • 6.8.4中序遍历二叉树
      • 6.8.5后序遍历二叉树
    • 6.9二叉树的建立
    • 6.10线索二叉树
    • 6.10.2线索二叉树结构-代码没看
    • 6.11树、森林与二叉树的转换
      • 6.11.1树转换为二叉树
      • 6.11.2森林转换为二叉树
      • 6.11.3二叉树转化为树
      • 6.11.4二叉树转化为森林
      • 6.11.5树与森林的遍历
    • 6.12赫夫曼树及其应用
      • 6.12.2赫夫曼树定义与原理
      • 6.12.3赫夫曼树编码

——《大话数据结构》的搬运工

第二章

2.9算法时间复杂度

2.9.2推导大O阶方法

计算时间复杂度
输入规模-操作数量f(n)

  1. 用常数1取代运行时间中的所有加法常数
  2. 在修改后的运行次数函数中,只保留最高阶项
  3. 如果最高阶项存在且不是1,则去除与这个项相乘的常数
  4. 得到大O阶

2.9.5对数阶

int count=1;
while(count<n)
{
	count=cout*2;
}

输入规模n,要操作多少次能够=n,有多少个2(乘多少次2)相乘就能够大于n,2x=n, x=log2n,也就是需要操作log2n次

O(1)2)3)n)n)

2.11最坏情况与平均情况

一般提到的运行时间都是最坏情况下的运行时间
平均时间复杂度:计算所有情况的平均值

2.12算法空间复杂度

算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作s(n)=O(f(n)),n为问题的规模,f(n)为语句关于n所占存储空间的函数——没懂

第三章 线性表

3.4线性表的顺序存储结构

3.4.3数据长度与线性表长度区别

数组点的长度不等于线性表的长度,数组长度是存放线性表的存储空间的长度,分配后不变,线性表长度是线性表中数据元素的个数,可以变化
线性表的长度应该小于等于数组的长度

3.5顺序存储结构的插入与删除

线性表的顺序存储结构,在存、读数据时,复杂度为O(1)。在删除和插入时,复杂度为O(n)。

3.6线性表的链式存储结构

头指针:链表中第一个节点的存储位置叫做头指针。头指针是链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。头指针不能为空,长以链表的名字命名。是链表的必要元素
头结点的指针域存储指向第一个结点的指针。头结点不是必要元素
注意头结点和头指针的区别
python-数据结构与算法补充1_第1张图片
python-数据结构与算法补充1_第2张图片

3.8单链表的插入与删除

python-数据结构与算法补充1_第3张图片
在删除链表元素的时候,注意要释放要删除的指针

q=p->next;
p->next=q->next;
free(q);

3.13循环链表

python-数据结构与算法补充1_第4张图片

p=rearA-_next;
rearA->next=rearB->next->next;
rearB->next=p;
free(p);

3.14双向链表

typedef struct DulNode
{
	ElemType data;
	struct DulNode* prior;
	struct DulNode* next;
}DulNode, *DuLinkList;//可以把DuLinkList等价于 struct DulNode* 

插入元素-记忆

s->prior=p;
s->next=p->next;
p->next->prior=s;
p->next=s;

删除元素-记忆

p->prior->next=p->next;
p->next->prior=p->prior;
free(p);

第四章 栈与队列

栈是限定仅在表尾进行插入和删除操作的线性表
队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表
栈顶:允许插入和删除的一端,另一端叫栈底
空栈:不含任何数据元素
栈:后进先出的线性表,LIFO结构

4.4栈的顺序存储结构及实现

4.4.1栈的顺序存储结构

通常把空栈的判断条件定为top=-1

typedef int SElemType;
typedef struct
{
	SElemType data[MAXSIZE];
	int top;
}SqStack;

4.5两栈共享空间

栈的顺序存储结构必须事先知道数组存储空间的大小
用一个数组存储两个栈,一个栈底为数组首元素,一个栈底为数组末端,增加栈元素则是想数组中间延伸

typedef struct
{
	SElemType data[MAXSIZE];
	int top1;
	int top2;
}SqDoubleStack;

4.6栈的链式结构及实现

把栈顶放在单链表的头部,对于栈链来说,不需要头结点

typedef struct StackNode
{
	SElemType data;
	struct StackNode* next;
}StackNode,*LinkStackPtr;

typedef struct LinkStack
{
	LinkStackPtr top;//相当于struct StackNode* top
	int count;
}LinkStack

4.6.2栈的链式存储结构——进栈操作

status Push(LinkStack* S,SElemType e)
{
	LinkStackPtr s=(LinkStackPtr)malloc(sizeof(StackNode));
	//相当于struct StackNode* s=(struct StackNode*)malloc(StackNode);
	s->data=e;
	s->next=S->top;//把当前的栈顶元素赋值给新节点的直接后继
	//二者都是struct StackNode*类型
	S->top=s;//将新的节点s赋值给栈顶指针
	S->count++;
	return OK:
}

4.6.3栈的链式存储结构——出栈操作

status Push(LinkStack* S,SElemType* e)
{
	LinkStackPtr p;
	if(StackEmpty(*s))
	{
		return ERROR;
	}
	*e=S->top->data;
	p=S->top;//将栈顶指针赋给p
	S->top=S->top->next;//使得栈顶指针下移一位,指向后一个节点
	free(p);
	S->count--;
	return OK:
}

链栈的进出的时间复杂度为O(1)

4.8栈的应用——递归

函数调用函数自己,可以看成调用一个与自己长得一样的另一个函数
在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中,在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分

4.10队列的定义

先进先出的功能。队列是只允许在一段进行插入操作,而在另一端进行删除操作的线性表
允许插入的一端为队尾,允许删除的一端为队头。

4.12循环队列

front指针指向队头元素,rear元素指针指向队尾元素的下一个位置,当front=rear时,此队列不是还剩一个元素,而是空队列
初始状态,front和rear均指向下标为0的位置,然后入队
循环队列解决假溢出的问题

//结构
typedef int QElemType;
typedef struct
{
	QElemType data[MAXSIZE];
	int front;//头指针
	int rear;//尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;

//循环队列初始化
Status InitQueue(SqQueue* Q)
{
	Q->front=0;
	Q->rear=0;
	return OK;
}

//循环队列求队列长度
int QueueLength(SqQueue Q)
{
	return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}

//循环队列的入队
Status EnQueue(SqQueue*Q,QElemType e)
{
	if((Q->rear+1)%MAXSIZE==Q->front)
		return ERROR;
	Q->data[Q->rear]=e;
	Q->rear=(Q->rear+1)%MAXSIZE;//rear指针后移一维,若到最后则转到数组头部
	return OK;
}

//队列出队
Status DeQueue(SqQueue*Q,QElemType e)
{
	if(Q->front==Q->rear)
		return ERROR;
	*e=Q->data[Q->front];
	Q->front=(Q->front+1)%MAXSIZE;//front指针向后移一位置,若到最后则转到数组头部
}

4.13队列的链式存储结构及实现

队列的链式存储结构,就是线性表的单链表,只不过只能尾进头出,称之为链队列
令队头指针指向链队列的头结点,队尾指针指向终端结点(不是终端结点的下一个,注意)
空队列时,front和rear都指向头结点

typedef int QElemType;

typedef struct QNode;
{
	QElemType data;
	struct QNode* next;
}QNode,*QueuePtr;

typedef struct
{
	QueuePtr fonrt,rear;//相当于struct QNode* front,*rear;
}LinkQueue;

4.13.1队列的链式存储结构-入队操作

Status EnQueue(LinkQueue* Q, QElemType e)
{
	QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
	if(!s)
		exit(OVERFLOW);
	s->data=e;
	s->next=NULL;
	Q->rear->next=s;
	Q->rear=s;
}

4.13.2队列的链式存储结构-入队操作

Status EnQueue(LinkQueue* Q, QElemType* e)
{
	QueuePtr p;
	if(Q->front==Q->rear)
		return ERROR;
	p=Q->front->next;
	*e=p->data;
	Q->front->next=p->next;
	if(Q->rear==p)
		Q->rear=Q->front;
	free(p);
	return OK;
}

第5章 串

5.6朴素的模式匹配算法

子串的定位称为串的模式匹配

//主串长度和子串长度存放在S[0]和T[0]中
int Index(String S,String T,int pos)
{
	int i=pos;
	int j=1;
	while(i<=S[0]&&j<=T[0])
	{
		if(S[i]==T[j])
		{
			i++;
			j++;
		}
		else
		{
			i=i-j+2;
			j=1;
		}
	}
	if(j>T[0])
		return i-T[0];
	else
		return 0;
}

5.7KMP模式匹配算法-代码没懂

核心就是

  • 比较指针不回溯
  • 判断前缀、后缀
  • 构建模式串(子串)的next数组值
//通过计算返回子串中的next数组
void get_next(String T,int *next)
{
	int i,j;
	i=1;//数组从1开始
	j=0;
	next[1]=0;//next[1]一般都是0
	while(i<T[0])//T[0]记录子串的长度
	{
		if(j==0||T[i]==T[j])//T[i]表示后缀的单个字符;T[j]表示前缀的单个字符
		{
			i++;
			j++;
			next[i]=j;
		}
		else
		{
			j=next[j];
		}
	}
}

第六章 树

6.2树的定义

度:结点拥有的子树数
度为0的结点称为叶结点
树的度为树内各结点的度的最大值
孩子,双亲,兄弟
结点的祖先是从根到该结点所经分支上的所有结点
注意:该结点的子树的根,不是该结点,是该结点的孩子
层次:树的层次从根开始,根为第一层,以此类推(结点在l层,该结点的子树的根在l+1层)
深度:树中结点的最大层次叫深度(高度)
将树中结点的各子树看成从左至右是有次序的,不能互换的,则称为有序树,反之为无序树
森林:m棵互不相交的树的集合。对树中的每个结点而言,其子树的集合即为森林

6.4树的存储结构

6.4.1双亲表示法

设一组连续空间存储树的结点,同时每个结点中,附设一个指示器指示双亲结点到链表中的位置[data,parent],data存储结点的数据信息,parent指针域,存储该结点的双亲在数组中的下标

#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct PTNode
{
	TElemType data;//结点数据
	int parent;//双亲位置
}PTNode;

typedef struct
{
	PTNode nodes[MAX_TREE_SIZE];//结点数组
	int r,n;//根的位置和结点数
}PTree;

6.4.2孩子表示法

树中的每个结点可能有多棵子树,考虑用多重链表,每个结点有多个指针域,其中每个指针域指向一颗子树的根结点,该方法为多重量表表示法。但是树的每个结点的度不同
方案一:
指针域的个数等于树的度

data,child1,...,childN

缺点:当树中的各结点的度相差很大时,会浪费空间,很多结点的指针域是空的
方案二
每个结点指针域的个数等于该结点的度,专门用一个位置存储该结点指针域的个数

data,degree,child1,...,childM

缺点:维护起来麻烦
孩子表示法
把每个结点的孩子结点排列起来,以单链表作为存储结构,n个几点有n个孩子链表,如果是叶子结点单链表为空。然后n个头指针又自成一格线性表,采用顺序存储结构,放进一个一维数组中
python-数据结构与算法补充1_第5张图片
两种结点结构:
孩子链表结构

child,next
child为数据域,用于存储某个结点在表头数组中的下标,就是该结点的第一个孩子结点所在表头数组中的位置
next为指针域,用来存储指向某结点的下一个孩子结点的指针,就是该结点(表头结点)的下一个孩子,即第二个、第三个孩子,就是第一个孩子的兄弟

表头数组的表头结点

data,firstchild
firstchild存储该结点的孩子链表的头指针
//孩子表示法
#define MAX+TREE_SIZE 100
//孩子结点
typedef struct CTNode
{
	int child;
	struct CTNode* next;
}*ChildPtr;

//表头结构
typedef struct
{
	TElemType data;
	ChildPtr firstchild;//包含了孩子链表的头指针
}CTBox;

typedef struct
{
	CTBox nodes{MAX_TREE_SIZE};//结点数组
	int r,n;//根的位置和结点数
}

双亲孩子表示法
python-数据结构与算法补充1_第6张图片

6.4.3孩子兄弟表示法

任意一棵树,结点的第一个孩子如果存在就是唯一的,他的右兄弟如果存在也是唯一的。设置两个结点指向该结点的第一个孩子和此结点的右兄弟

data,firstchild,rightsib
typedef struct CSNode
{
	TElemType data;
	struct CSNode* firstchild,*rightsib;
}CSNode,*CSTree;

图P162

6.5二叉树的定义

每个根结点只有两个子树,左子树和右子树,二叉树的度最大为2
左右子树是由顺序的,次序不能颠倒。即使某结点只有一颗子树,也要区分是左子树和右子树
二叉树的五种形态

空二叉树
只有一个根结点
根结点只有左子树
根结点只有右子树
根结点有左右子树

6.5.2特殊二叉树

  1. 斜树

所有结点都只有左子树为左斜树
所有结点都只有右子树为右斜树
即每一层只有一个结点,结点的个数与二叉树的深度相同

  1. 满二叉树

所有分支结点都有左右子树,且所有的叶子都在同一层上
叶子只出现在最下一层
非叶子的结点的度都是2
同样深度的二叉树中,满二叉树的结点个数最多

  1. 完全二叉树

当等于就是按层序编号,与满二叉树层序编号的结果一致,但是叶子可能不全在最下一层
叶子结点只能出现在最下两层
最下层的叶子一定集中在左部连续位置
倒数二层,若有叶子结点,一定都在右部连续位置
如果结点度为1,则该结点只有左孩子,一定没有右孩子
同样结点数的二叉树,完全二叉树的深度最小

6.6二叉树的性质

二叉树的第i层上至多有2i-1个结点

深度为k的二叉树最多有2k-1个几点

任何一棵树,终端结点数(叶子结点数)为 n 0 n_0 n0,度为2的结点数为 n 2 n_2 n2,则 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
推导:
n 1 n_1 n1为度为1的结点数, n 1 2 n_12 n12为度为2的结点数,总结点数为 n n n,总分支数为 = n − 1 = n 1 + 2 n 2 ( 没 懂 ) =n-1=n_1+2n_2(没懂) =n1=n1+2n2
n 0 + n 1 + n 2 − 1 = n 1 + 2 n 2 : − > n 0 = n 2 + 1 n_0+n_1+n_2-1=n_1+2n_2 :->n_0=n_2+1 n0+n1+n21=n1+2n2:>n0=n2+1

n个结点的完全二叉树的深度为 ( l o g 2 n ) + 1 (log_2n)+1 (log2n)+1,表示不大于 ( l o g 2 n ) (log_2n) (log2n)的整数,然后再+1
对于满二叉树,深度为k,结点数为n= 2 k − 1 2^k-1 2k1,k= l o g 2 ( n + 1 ) log_2(n+1) log2(n+1)

python-数据结构与算法补充1_第7张图片

6.7二叉树的存储结构

利用顺序存储结构来定义二叉树,最简单的是定义完全二叉树,数组的对应下标能够反映结点之间的逻辑关系。对于不是完全二叉树的任意形式二叉树,可以按照完全二叉树进行存储,不存在的结点在数组中设置为空。这样会浪费空间,因此一般顺序存储结构只用于完全二叉树

6.7.2二叉链表

每个结点最多有两个孩子,可以设置两个指针

lchild,data,rchild

结点结构

typedef struct BiTNode
{
	TElemType data;
	struct BiNode* lchild,*rchild;
}BiTNode,*BiTree;

6.8遍历二叉树

  • 前序遍历——根-左-右
  • 中序遍历——左-根-右
  • 后序遍历——左-右-根
  • 层序遍历——按层,从左到右

6.8.3前序遍历二叉树

void PreOrderTraverse(BiTree T)
{
	if(T==NUll)
		return;
	printf("%c",T->data);//按字符输出
	PreOrderTraverse(T->lchild);//左孩子,lchild和rchild和T都是struct BiTNode*结构的
	PreOrderTraverse(T->rchild);//右孩子
}

注意:当函数执行完毕的时候,自动返回到上一级递归的函数中去
具体递归图解见书P180

6.8.4中序遍历二叉树

void InOrderTraverse(BiTree T)
{
	if(T==NUll)
		return;
	InOrderTraverse(T->lchild);//左孩子,lchild和rchild和T都是struct BiTNode*结构的
	printf("%c",T->data);//按字符输出
	InOrderTraverse(T->rchild);//右孩子
}

具体递归图解见书P183

6.8.5后序遍历二叉树

void PostOrderTraverse(BiTree T)
{
	if(T==NUll)
		return;
	PostOrderTraverse(T->lchild);//左孩子,lchild和rchild和T都是struct BiTNode*结构的
	PostOrderTraverse(T->rchild);//右孩子
	printf("%c",T->data);//按字符输出
}

已知前序和中序,得唯一二叉树
已知后序和中序,得唯一二叉树
已知前序和后序,不能确定二叉树

6.9二叉树的建立

扩展二叉树:将每个结点的空指针引出一个虚结点,其值为一个特定值,如#

//假设二叉树的结点均为一个字符
//按照前序输入二叉树中结点的值(一个字符)
//#表示空树,构造二叉链表表示二叉树$T_0$
void CreateBiTree(BiTree* T)
{
	TElemType ch;//字符型
	scanf("%c",&ch);//输入字符
	if(ch=='#')
		*T=NUll;
	else
	{
		*T=(BiTree)malloc(sizeof(BiTNode));//开辟一个结点,BiTree为struct BiTNode*
		if(!*T)
			exit(OVERFLOW);
		(*T)->data=ch;//生成根结点
		CreateBiTree(&(*T)->lchild);//构建左子树
		CreateBiTree(&(*T)->rchild);//构建右子树		
	}
}

6.10线索二叉树

加上线索的二叉链表为线索链表,加上线索的二叉树为线索二叉树

python-数据结构与算法补充1_第8张图片

6.10.2线索二叉树结构-代码没看

线索化的实质就是将二叉链表中的空指针改为指向前驱或后去的线索
由于前驱和后继的信息只有在遍历二叉树时才能得到,所以线索化的过程就是在遍历的过程中修改空指针的过程

typedef enum{Link,Thread}PointerTag;//enum声明了一个枚举类型,link==0表示指向左右孩子指针,Thread==1表示指向前驱后继指针
/*
这个相当于结构体
typedef enum
{
	Link,
	Thread,
}PointerTag;
*/

typedef struct BiThrNode
{
	TElemType data;
	struct BiThrNode* lchild,*rchild;
	PointerTag LTag;
	PointerTag RTag;	
}BiThrNode,*BiThrTree;
//中序遍历线索化
BiThrTree pre;//式中指向刚刚访问过的结点
void InThreading(BiThrTree p)
{
	if(p)
	{
		InThreading(p->lchild);//递归左子树线索化
		//###
		
	}
}

6.11树、森林与二叉树的转换

利用孩子兄弟表示法,进行转换

6.11.1树转换为二叉树

加线。在所有兄弟之间加一条线
去线。对树中每个结点,只保留它与第一个孩子结点的连线,删除它与其他孩子结点之间的连线
层次调整。以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。注意第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子是节点的右孩子。
实例见图

6.11.2森林转换为二叉树

森林中的每一棵树都是兄弟,可以按照兄弟的处理办法来操作
把每棵树都转化为二叉树
第一棵二叉树不动,从第二棵二叉树开始,依次把后一颗二叉树的根结点作为前一棵二叉树的根结点右孩子,用线连接起来,当所有的二叉树连接起来后就得了由森林转化来的二叉树

6.11.3二叉树转化为树

加线。若某结点的左孩子结点存在,则将这个左孩子的右结点、右孩子的右孩子结点,即左孩子的n个右孩子结点都作为都作为此结点的孩子,将该结点与这些右孩子结点用线连接起来
去线。删除原二叉树中所有结点与其右孩子结点的连线
层次调整。使之结构层次分明

6.11.4二叉树转化为森林

判断二叉树能否转化为树或森林。只要看这棵二叉树的根结点有没有右孩子,有就是森林,没有就是一棵树。
从根结点开始,若右孩子存在,则把与右孩子结点的连线删除,再查看分离后的二叉树,若右孩子存在,则连线删除。直到所有右孩子连线都删除为止,得到分离的二叉树。
再将每棵分离后的二叉树转换为树即可

6.11.5树与森林的遍历

树的遍历:
先根遍历。即先访问树的根结点,即先访问根。然后再访问根的没棵子树
后根遍历。先访问根的子树,再访问根
森林的遍历:
前序遍历。先访问森林中的第一棵树的根结点,然后再依次先根遍历根的没棵子树。然后遍历第二棵树
后序遍历。先访问森林中的第一棵树,再按后根遍历方法

6.12赫夫曼树及其应用

基本的压缩编码法

6.12.2赫夫曼树定义与原理

结点的带权的路径长度,为从该结点到树根之间的路径长度与结点上权的乘积
树的带权的路径长度,为树中所有叶子结点的带权路径长度之和
赫夫曼树(最优二叉树):带权路径长度WPL最小的二叉树
赫夫曼树的构造:书P204
赫夫曼算法:
python-数据结构与算法补充1_第9张图片

6.12.3赫夫曼树编码

构造赫夫曼树,并将权值做分支变为0,将权值右分支变为1。
然后对每个叶子结点利用路径经过的01进行编码
利用赫夫曼树进行解码——没细讲,没懂

你可能感兴趣的:(算法与和数据结构,数据结构,链表,算法)