@skiery
数据结构算法分析第二版
1.引论
递归四原则:
1.基准情形:必须有无需递归就能得到的基准情形(如x=0,x=1)等情形下,直接得到结果。
2.不读阿奴推进:每次递归调用都必须使求解状况朝接近基准情形的方向推进。
3.设计法则:所有情况下的递归调用都能运行。
4.合成效益法则:求解同问题的同一个实例时,切勿在不同的递归调用中做重复性工作。(故斐波那契数列不建议递归)
2.算法复杂度分析
运行时间分析案例:
最大子序和
//暴力循环
int MaxSubsequenceSum0(const int a[], int N) {
int ThisSum, MaxSum;
MaxSum = 0;
for (int i = 0; i < N; i++) {
for (int j = i;j<N;j++)
{
ThisSum = 0;
for (int k = i; k <= j; k++) {
ThisSum += a[k];
}
if (ThisSum > MaxSum) {
MaxSum = ThisSum;
}
}
}
return MaxSum;
}
//遍历循环每个子序
int MaxSubsequenceSum1(const int a[], int N) {
int ThisSum,MaxSum;
MaxSum = 0;
for (int i = 0; i < N; i++) {
ThisSum = 0;
for (int j = i; j < N; j++) {
ThisSum += a[j];
if (ThisSum > MaxSum) {
MaxSum = ThisSum;
}
}
}
return MaxSum;
}
//分治
static int MaxSubsequenceSum2(const int a[], int Left, int Right) {
int MaxLeftSum, MaxRightSum;
int MaxLeftBorderSum, MaxRightBorderSum;
int LeftBorderSum, RightBorderSum;
int Center;
if (Left == Right) {
if (a[Left] > 0)
{
return a[Left];
}
else {
return 0;
}
}
Center = (Left + Right) / 2;
MaxLeftSum = MaxSubsequenceSum2(a, Left, Center);
MaxRightSum = MaxSubsequenceSum2(a, Center + 1, Right);
MaxLeftBorderSum = 0; LeftBorderSum = 0;
for (int i = Center; i >= Left; i--) {
LeftBorderSum += a[i];
if (LeftBorderSum > MaxLeftBorderSum) {
MaxLeftBorderSum = LeftBorderSum;
}
}
MaxRightBorderSum = 0; RightBorderSum = 0;
for (int i = Center + 1; i <= Right; i++) {
if (RightBorderSum>MaxRightBorderSum)
{
MaxRightBorderSum = RightBorderSum;
}
}
return max(max(MaxLeftSum, MaxRightSum), MaxLeftBorderSum + MaxRightBorderSum);
}
用的最多:
//动态规划
int MaxSubsequneceSumdy(const int a[], int N) {
int ThisSum, MaxSum;
ThisSum = MaxSum = 0;
for (int i=0;i<N;i++)
{
ThisSum += a[i];
if (ThisSum > MaxSum) {
MaxSum = ThisSum;
}
else if (ThisSum < 0) {
ThisSum = 0;
}
}
return MaxSum;
}
3.线性表
3.1顺序表
特点:储存空间地址连续
数组实现:
//int a[];
int *a;
*a = 1;
*(a+1) = 2;
char* a = (char*)malloc(sizeof(char)*128);
//申请一个长度为128的字符串数组,返回头指针
数组插入,删除:
int a[10]={};
//delete 9th
for(int i=9-1;i<10-1;i++){
a[i]=a[i+1];
}
3.2链表
特点:链式存储
3.2.1单链表
建立单链表:头插法、尾插法
typedef struct Node{
int data;
struct Node *next;
}Node,*LinkList;
//c++定义链表节点
class Node<int>{
int item;
Node<int> next;
Node(int element){
this.item = element;
this.next = null;
}
}
//尾部插入
Node *ptr = new Node(1);
Ntail->next = ptr;
//头部插入
Node *ptr = new Node(1);
ptr->next = head;//(head为头指针)
//中间插入
Node *ptr = new Node(1);
ptr->next = Npre->next;
Npre->next = ptr;
//按序号查找节点值
循环遍历所有结果再比较
//按值查找表节点
循环遍历
//求表长
循环遍历(一般情况下保存在头结点中)
3.2.2双链表
//双链表的插入
Node *ptr = new Node(1);
ptr->next = Npre->next;
ptr->pre = Nnext->pre;
Npre->next = ptr;
Nnext->pre = ptr;
//双链表的删除
Node *ptr; //需要删除的节点
Npre->next = ptr->next;
Nnext->pre = ptr->pre;
3.2.3循环链表
//判断是否为空
头结点指针是否等于头节点
if(head->next == head)
//插入删除同单链表相同
3.2.4静态链表
特点:需要提前分配连虚空间?????
??
4. 栈
4.1顺序栈
1.栈的指针指向栈顶 :为空时S.top = -1
2.栈空条件:栈空:S.top = -1;栈满:S.top = MaxSize-1;栈长:S.top+1
3.进栈:栈顶指针加1,再加元素
4.出栈:先取出元素,再指针减一
4.2链栈
所有操作都在该链表的表头进行
//入栈
Node *ptr = new Node(1);
ptr->next = stop;
//出栈
head = head->next;
4.3共享栈
特点:两个顺序栈共享一个一维数据空间
更有效利用空间??
int a[10];
a[0-5]; a[6-9];
5.队列
5.1顺序队列
//队列为空
Q.front == Q.rear == 0;
//进队
队尾先进值再指针加1
//出队
先取队头值再队头指针加1
5.2循环队列
与实际应用中使用的队列结构相仿
//初始
Q.front = Q.rear = 0;
//队空
Q.front = Q.rear;
//队列元素个数
(Q.front-Q.front+MaxSize)&MaxSize
5.3链式队列
链表实现的队列
5.4双端队列
双向队列
6.树
1对1为线性结构;
1对多为树型结构。
6.1二叉树
6.1.1二叉树顺序存储
用一组地址连续的存储单元,从上到下,从左到右存储完全二叉树上的结点元素。
完全二叉树:直接处理就行。
typedef struct TreeNode()
一般二叉树:空位置处加一个空指针(因此有些情况浪费大)
6.1.2二叉树的链式存储
typedef struct BitNode{
int data;
struct BitNode *lchild,*rchild;
}BitNode,*BitTree;
6.2二叉树的遍历
二叉树的遍历指按某种次序依次访问树中的每个节点,使得每个节点均被访问依次,而且仅被访问依次。
遍历方法分为:
6.2.1先序遍历
先根、再先序遍历左子树、再先序遍历右子树
1.递归
void InOrder(BitTree T){
if(T!=NULL){
printf("%c",T->data);
InOrder(T->lchild);//遍历递归左子树
InOrder(T->rchild);//遍历递归右子树
}
}
2.非递归
void PreOrder(BitTree T){
InitStack(S);
BitTree p=b; //工作指针
while(p||!IsEmpty(S)){
while(p){
printf("%c",p->data);
Push(S,p);
p = p->lchild;
}
if(!IsEmpty(S)){
p=Pop(S);
p=p->rchild;
}
}
}
6.2.2中序遍历
先左子树,再访问根节点,再访问右子树
1.递归
void InOrder(BitTree T){
if(T!=NULL){
InOrder(T->lchild);//递归左子树
printf("%c",T->data);
InOrder(T->rchild);//递归右子树
}
}
2.非递归
void MidOrder(BitTree T){
InitStack(S);
BitTree m;
m = T;
while(m||!IsEmpty(S)){
while(m){
Push(S,m);
m=m->lchild;
}
if(!IsEmpty(S)){
m=Pop(S);
printf("%c",m);
m = m->rchild;
}
}
}
6.2.3后序遍历
先左子树,再右子树,最后根结点
1.递归
void InOrder(BitTree T){
if(T!=NULL){
InOrder(T->lchild);//递归左子树
InOrder(T->rchild);//递归右子树
printf("%c",T->data);
}
}
2.非递归
void AftOrder(BitTree T){
InitStack(S);
BitTree a = T;
BitTree r=NULL;
while(a||!IsEmpty(S)){
if(a){
Push(S,a);
a = a->lchild;
}
else{
a = S.Top();
if(a->rchild&&a->rchild!=r) a=a->rchild;
else{
Pop(S,a);
printf("%c",a->data);
r = a; //指向访问过的节点
a = NULL; //使p为空从而继续访问栈顶
}
}
}
}
6.2.4层序遍历
层序遍历过程:若树为空,直接返回;否则从树的第一层开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
出队–>访问–>左右孩子入队
void LevelOrder(BitTree T){
InitQueue(Q);
BitTree p;
EnQueue(Q,T);
while(!IsEmpty(Q)){
DeQueue(Q,p);
printf("%c",p->data);
if(p->lchild!=NULL){
EnQueue(Q,p->lchild);}
if(p->rchild!=NULL){
EnQueue(Q,p->rchild);}
}
}
6.3线索二叉树
二叉链表示二叉树存在大量的空指针
复杂且偏,暂未整理
哈夫曼树和哈夫曼编码
高频出现项使用更短的编码。