66个代码带你通关数据结构代码题,最适合学习数据结构代码的博客之一

数据结构基础代码

前言

​ 相信很多同学有遇到学习完了数据结构但是对考试中或者面试题里的代码部分一窍不通。

​ 在最近备考中我整理了以下这些数据结构的代码,这些代码我默写了两个月,让我对数据结构的整体理解有了极大的帮助。

​ 当你能理解并且流畅默写出以下代码,相信你也可以和我一样,手撕王道数据结构里的代码题。

​ PS:( 以下使用C语言 )

​ PS:( 由于还在备考阶段,这里只会对代码部分作出详细注释,至于数据结构的基本逻辑等考完有时间再补上 )

​ 废话不多说,开始学习!

第一章 线性表

1、顺序表的定义及初始化


 #define MaxSize 50
 typedef struct { //typedef的作用相当于给你定义的结构体设置一个小名,叫王天霸太严肃的话 typedef 王天霸 小甜甜,叫他甜甜吧
 	int data[MaxSize];
 	int length;
 }SqList;
 
 typedef struct { //typedef的作用相当于给你定义的结构体设置一个小名,叫王天霸太严肃的话 typedef 王天霸 小甜甜,叫他甜甜吧
 	int *data; //定义指针存储数组,与直接定义数组的区别就是初始化不同
 	int length;
 }SqList;
 
 void Init(SqList &l){
     l.length = 0;
 }

 void Init(SqList &l){
     l = new int[MaxSize];
     l.length = 0;
 }

2、链表的定义及初始化

 #define MaxSize 50
 typedef struct LNode{
 	int data;
 	struct LNode *next;
 }LNode,*LinkList;

 void Init(LinkList &l){ //采用头结点,因为不用头结点太麻烦,题目如果不给你头结点你自己安一个他能把你怎么样
     l = new LNode;
     l -> next = NULL;
 }

3、链表头插法

​ 即给定一个数组在链表中逆序排列。

 LinkList List_HeadInsert(LinkList &L, int arr[],int n){//头插法建立链表
 	LNode *s; //存储待插入节点
 	Init(L); //初始化链表
 	for(int i = 0 ; i < n ; i++){
 		s = new LNode;
 		s->data = arr[i];
 		s->next = L->next; //头插
 		L->next = s;
 	}
 	return L;
 }

4、链表尾插法

​ 即给定一个数组在链表中顺序排列。

 LinkList List_TailInsert(LinkList &L,int arr[],int n){//尾插法
 	Init(L); //初始化链表
    LNode *s; //存储待插入节点
    LNode *r = L; //指向链表最末节点,方便直接插入
 	for(int i = 0 ; i < n ; i++){
 		s = new LNode;
 		s->data = arr[i];
 		s->next = NULL; //尾插
 		r->next = s;
        r = r-> next;
 	}
 	return L;
 }

5、将两个递增的有序链表合并为一个递增的有序链表。要求结果链表仍使用原来两个链表的存储空间,不另外占用其他的存储空间。表中不允许有重复的数据。

​ 递增的链表给予我们的条件是每次可以通过比较选出最小的元素,应用尾插法即可实现。

 void MergeList(LinkList &La, LinkList &Lb, LinkList &Lc){
	LNode *pa = La->next; //pa 指向La的元素
	LNode *pb = Lb->next; //pb 指向Lb的元素
	LNode *pc;
	Lc= pc = La; //用 La 的头结点作为 Lc 的头结点
	while (pa&&pb){ 
		if (pa->data > pb->data){ // pb比pa小,就先插入pb
 			pc->next = pb; //插入pb,这里pc直接指向了pb节点,即题目要求的使用原有空间
 			pc = pc->next;
 			pb = pb->next; //pb后移
		}
 		else if(pa->data<pb->data){ //同理
 			pc->next = pa;
 			pc = pc->next;
			pa = pa->next;
		}
 		else{ //此时pa == pb,相等时候取 La 中的元素,删除 Lb 中的元素
 			pc->next = pa;
 			pc = pc->next;
			pa = pa->next;
			LNode *q; //删除pb指向的节点,pb后移
 			q = pb;
            pb = pb->next;
 			delete q;
 		}
 	}
 	pc->next = pa ? pa : pb; //将非空表的剩余元素直接链接在 Lc 表的最后
 	delete Lb;
 }

6、将两个递增的有序链表合并为一个递减的有序链表。要求结果链表仍使用原来两个链表的存储空间,不另外占用其他的存储空间。表中允许有重复的数据。

​ 上一题我们实现了递增的合并,这题我们可以引用上题的思想,由于递增链表每次我们都可以选出最小的元素插入LC,题目要求的递减我们不难想到可以利用头插法实现递减。PS:( 这里我们需要注意考虑La或者Lb为空的情况,当另一个表为空时直接选取不为空的表元素)

 void MergeList(LinkList &La, LinkList &Lb, LinkList &Lc){
	LNode *pa = La->next;
	LNode *pb = Lb->next;
	LNode *pc,*q;
 	Lc = pc = La; //用 La 的头结点作为 Lc 的头结点
	Lc->next = NULL;
 	while (pa||pb){ //只要有一个表不为空就继续
 		if (!pa){//La 表为空,用 q 指向 pb,pb 指针后移
			q = pb;
 			pb = pb->next;
 		}else if(!pb){//Lb 表为空,用 q 指向 pa,pa 指针后移
 			q = pa;
 			pa = pa->next;
		}else if(pa->data>pb->data){//取较小者 Lb 中的元素,用 q 指向 pb,pb 指针后移
 			q = pb;
 			pb = pb->next;
 		}else if(pa->data<=pb->data){//取较小者 La 中的元素,用 q 指向 pa,pa 指针后移
 			q = pa;
 			pa = pa->next;
 		}
 		q->next = Lc->next;//头插法
        Lc->next = q;
 	}
 	delete Lb;//释放 Lb 的表头结点
 }

7、已知两个链表 A 和 B 分别表示两个集合,其元素递增排列。请设计算法求出两个集合 A 和 B 的差集(即仅由在 A 中出现而不在 B 中出现的元素所构成的集合),并以同样的形式存储,同时返回该集合的元素个数。

​ 由于递增有序,我们可以利用比较,当pa小于pb时,此元素为差集元素。当相等时,删除La中的元素,pa,pb都向后移。当pb大于pa时无法判断pa是否属于差集,就让pb向后移。

 void Difference(LinkList &La, LinkList &Lb, int &n){//La 和 Lb 差集的结果存储于 La 中,n 是结果集合中元素个数,调用														//时为 0
	LNode *pa, *pb, *pre;//La涉及删除操作需要一个前置指针pre始终指向pa前一个元素
	pa = La->next;
    pb = Lb->next;
    pre = La;
	while (pa&&pb){//两个链表都不为空
		if (pa->data < pb->data){//pa小于pb,是差集元素
			pre = pa;
			n++;
 			pa = pa->next;
        }else if(pa->data > pb->data) //pb大于pa,pb向后移
			pb = pb->next;
		else{//在 La 表删除 La 和 Lb 中元素值相同的结点
			pre->next = pa->next;
			LNode *u = pa;
			pa = pa->next;
			delete u;
		}
	}
	while (pa){ //pa还有元素就继续添加
		n++;
		pa = pa->next;
	}
}

8、已知两个链表 A 和 B 分别表示两个集合,其元素递增排列。请设计一个算法,用于求出A 和 B 的交集,并存放在 A 链表中。

​ 通过上一道题的经验,我们可以很容易分析出相等时删除pb,pa向后移。不等时删除最小元素。

void Intersection(LinkList &La, LinkList &Lb, LinkList &Lc){
	LNode *pa, *pb, *pc,*u;
	pa = La->next;
    pb = Lb->next;
    Lc = pc = La;
	while (pa&&pb){
		if (pa->data == pa->data){
 			pc->next = pa; //取 La 中的元素
 			pa = pa->next;
 			u = pb; //删除 Lb 中的对应的相等元素
 			pb = pb->next;
 			delete u;
 		}else if(pa->data<pb->data){//删除较小者 La 中的元素
 			u = pa;
            pa = pa->next;
 			delete u;
 		}else{//删除较小者 Lb 中的元素
 			u = pb;
            pb = pb->next;
 			delete u;
 		}
 	}
 	while (pa){//Lb 为空,删除非空表 La 中的所有元素
 		u = pa;
        pa = pa->next;
 		delete u;
 	}
 	while (pb){//La 为空,删除非空表 Lb 中的所有元素
 		u = pb; 
        pb = pb->next;
 		delete u;
 	}
 	pc->next = NULL;//置链表 Lc 尾标记
 	delete Lb;//释放 Lb 的头结点
 }

9、元素逆置。设计一个算法,将链表中所有结点的链接方向“就地”逆转,既要求仅利用原表的存储空间,’就地‘ 要求算法的空间复杂度为 O(1)。

​ 当逆转与链表出现我们应该想到大名鼎鼎的头插法。

void Inverse(LinkList &L){
 	LNode *p, *q;
 	p = L->next; //p指向待排元素
 	L->next = NULL;
 	while (p!=NULL){
 		q = p->next; //q 保存待排链表,此时p为需头插的元素
 		p->next = L->next; //头插法
 		L->next = p;
 		p = q; //p重新指向待排链表,准备下一次循环
 	}
 }

第二章 栈和队列

10、顺序栈的定义和初始化

#define MaxSize 50
typedef struct {
    int data[MaxSize]; //这里不用指针演示了,那玩意初始化还得多一行代码,麻烦
 	int top;
}SqStack;

void Init(SqStack &S){
	 S.top = -1;
}

11、顺序栈的基本操作

bool IsEmpty(SqStack S){//判空
    return S.top == -1;
}

bool Push(SqStack &S, int x){//入栈
	if (S.top == MaxSize - 1)//判断是否栈满
		return false;
	S.data[++S.top] = x;
    return true;
}

bool Pop(SqStack &S, int &e){//出栈
	if (IsEmpty(S))//判断是否栈空
		return false;
	e = S.data[S.top--];
    return true;
}

bool GetTop(SqStack &S, int &e){//获取栈顶元素,和出栈区别于不需要改变栈顶指针
	if (!IsEmpty(S))//判断是否栈空
		return false;
	e = S.data[S.top];
    return true;
}

12、链栈的定义和初始化

typedef struct StackNode{ //和单链表的定义一模一样
 	int data;
 	struct StackNode *next;
}StackNode ,*ListStack;

void Init(LinkStack &S) {
 	S = new StackNode; //带头结点
 	S->data=-1;
 	S->next=NULL;
}

13、链栈的基本操作

bool IsEmpty(ListStack S){//判空
    return S->next == NULL;
}

void Push(ListStack &S, int x){//入栈,不存在入栈不成功的可能
	StackNode* p = new StackNode;
	p->data = e;
	p->next = s->next; //头插
	s->next = p;
}

bool Pop(ListStack &S, int &e){//出栈,相当于删除单链表第一个元素
	if (IsEmpty(S))//判断是否栈空
		return false;
	LinkStack p = S->next; 
 	e = p->data;
 	S->next = p->next;
 	delete p;
}

14、利用栈实现括号匹配

bool BracketsCheck(char *str){
	Stack S;
	Init(S);
 	int i = 0; 
    char e = null;//即char e = '\0';
 	while (str[i] != '\0'){ //表达式没到最尾
		switch (str[i]){//左括号入栈
 			case '(': Push(S, '('); break;
 			case '[': Push(S, '['); break;
 			case '{': Push(S, '{'); break;
 			case ')':
 				Pop(S, e);
 				if (e != '(') return false;
 				break;
 			case ']':
                Pop(S, e);
 				if (e != '[') return false;
                break;
 			case '}':
                Pop(S, e);
 				if (e != '{') return false;
                break;
 			default: break;
 		}
 		i++;
 	}
 	if (!IsEmpty(S)){
 		cout << "括号不匹配" << endl;
 		return false;
 	}
 	return true;
 }

15、求后缀表达式

​ 这个算法的讲解详情可以搜王道讲解的栈求后缀表达式,算法思想一致。

​ 代码虽然长,但是理解后其实无非就是,数字直接输出,栈空或左括号直接入栈,加减乘除出栈的限制要求不同,遇到右括号全部出栈后记得将左括号弹出,最后把剩余符号依次弹栈。

​ 理解后就特别简单不要死记硬背,希望同学们看王道的讲解好好去实现代码。

char* calculate(char s[]) {
	Stack stack;
	Init(stack);
    char* result[MaxSize];
    int n = 0; //记录结果数组下标
	for (int i = 0; i < sizeof(s); i++) //循环遍历算式
	{
		if (s[i] >= '0' && s[i] <= '9') {
			result[n++] = s[i]; //数字直接存入结果
		}
		else {//不是数字
			if (isEmpty(stack) || s[i] == '(') //栈为空或者字符是左括号时直接入栈
			{
				push(stack, s[i]);
			}
			else
			{
				char t; //储存栈顶元素
				if (s[i] == '+' || s[i] == '-') { //当字符为+或-时,一直弹出栈内元素直到遇见左括号时停止,最后将+号入栈
					while (getTop(stack, t) && t != '(') { 
						pop(stack, t);
						result[n++] = t;
					}
					push(stack, s[i]); //符号入栈
				}
				if (s[i] == '*' || s[i] == '/') {//当字符为*或/时,因为先计算乘除后加减,所以遇到+-(时停止出栈
					while (getTop(stack, t) && t != '(' && t != '+' && t != '_') {
						pop(stack, t);
						result[n++] = t;
					}
					push(stack, s[i]);//符号入栈
				}

				if (s[i] == ')') { //遇到)需要把括号内算式全部弹出,最后将(也出栈
					while (getTop(stack, t) && t != '(') {
						pop(stack, t);
						result[n++] = t;
					}
					pop(stack, t); //把(弹出来
				}
			}
		}
	}
	while (!isEmpty(stack)) { //剩余符号全部出栈
		char t;
		pop(stack, t);
		result[n++] = t;
	}
	return result;
}

16、计算后缀表达式

double Postfix(char A[]){
	Stack stack;
	Init(stack);
	char a, b; //存储 操作数
    int j = 0;
 	char *data=new char[50]; //拼数用的数组
 	while (A[j]!='$'){//表达式没有扫描完毕
 		int i = 0;
 		while ((A[j] >='0'&&A[j] <='9')|| A[j] =='.'){//拼数,将读入的数字或小数点依次保存在字符数组 data 中
 			data[i++] = A[j++];
 		}
 		float num=atof(data);//将字符串转换成浮点数,此函数需添加头文件#include<stdlib>
 		Push(stack,num);//操作数进栈
 		switch (A[j]){
 			case' ':break;//遇到空格,继续读入表达式中下一个字符
 			case'+':Pop(stack, b); Pop(stack, a); Push(stack, a + b); break;
			case'-':Pop(stack, b); Pop(stack, a); Push(OPND, a - b); break;
 			case'*':Pop(stack, b); Pop(stack, a); Push(stack, a * b); break;
 			case'/':Pop(stack, b); Pop(stack, a); Push(stack, a / b); break;
 		}
 		j++;
 	}
 	return GetTop(stack);
 }

17、顺序队列的定义和初始化

#define MaxSize 50
typedef struct {
    int data[MaxSize]; //这里不用指针演示了,那玩意初始化还得多一行代码,麻烦
 	int front,rear;
}SqQueue;

void Init(SqQueue &Q){
	 Q.front = Q.rear = 0;
}

18、顺序队列的基本操作

bool IsEmpty(SqQueue q){//判空
    return q.front==q.rear;
}

bool EnQueue(SqQueue &Q, int x){//入队
	if ((Q.rear + 1) % MaxSize == Q.front)//这里牺牲一个位置来判断是否队满
		return false;
	Q.data[Q.rear] = x;
    Q.rear = (Q.rear + 1) % MaxSize;
    return true;
}

bool DeQueue(SqQueue &Q, int &x){//出队
	if (IsEmpty(Q))//判断是否队空
		return false;
	x = Q.data[Q.front];
    Q.front = (Q.front + 1) % MaxSize;
    return true;
}

19、链队列的定义和初始化

typedef struct LinkNode{
 	int data;
 	struct LinkNode *next;
}LinkNode;
typedef struct{
	LinkNode *front, *rear; //也是单链表,这里队头为头结点。
}LinkQueue;

void Init(LinkQueue &Q){
	Q.front = Q.rear = new LinkNode;
 	Q.front->data = -1;
 	Q.front->next = NULL;
}

20、链队列的基本操作

bool IsEmpty(LinkQueue q){//判空
    return q.front==q.rear;
}

void EnQueue(LinkQueue &Q, char e){//入队
	LinkNode *p = new LinkNode; //熟悉的尾插法
 	p->data = e;
 	p->next = NULL;
 	Q.rear->next = p;
	Q.rear = p;
}

bool DeQueue(LinkQueue &Q, char &e){//出队
    if (IsEmpty(Q))//判断是否队空
        return false;
    LinkNode *p = Q.front->next;
    e = p->data; // 获取队头元素的值
    Q.front->next = p->next;
    if (Q.rear == p) { // 判断是否为最后一个元素
        Q.rear = Q.front;
    }
    delete p;
    return true;
}

第三章 数组

21、递归求数组内最大值

int Get_Max(int a[],int n){
	if (n <= 0)
		return a[0];
	else 
        return a[n-1] > Get_Max(a,n-1) ? a[n-1]: Get_Max(a,n-1);
}

22、递归求数组和

int Get_Sum(int a[], int n){
	if (n == 1)
		return a[n - 1];
	return Get_Sum(a, n - 1) + a[n - 1];
}

23、递归求数组平均值

int Get_Average(int a[], int n){
    if (n == 1)
        return a[0];
    else
        return (Get_Average(a, n - 1) * (n - 1) + a[n - 1]) / n;//(前n-1个的平均值 * (n-1) + 最后一个) / n,简单                                                                 //的数学
}

24、把数组内所有奇数放到偶数前面

void Adjust(int a[], int n){ //采用快排的思想,i找到前面的偶数,j找到后面的奇数互换位置,此算法会改变元素相对位置
	int i=0,j=n-1;
    while( i < j ){
        while( i < j && a[j] % 2 == 0) j--; //a[j]为偶数继续向前找
        while( i < j && a[i] % 2 == 1) i++; //a[i]为奇数继续向后找
        if( i < j ){
            swap(a,i,j);
            i++;
            j--;
        }
    }
}

第四章 字符串

25、字符串的定义

typedef struct{
    char data[Max]; //0下标不用
    int len; //长度
}String;

26、求子串

bool SubString(String s,String &sub,int pos,int length){//pos起始下标,len子串长度
    if(pos+length-1 > s.len)
        return false;
    for(int i = 1 ; i <= length ; i++){
        sub.data[i] = s.data[i+pos-1]; //这里不要死记,自己举个例子看一下第一个字符对应第几个位置,很简单推的
    }
    sub.len = length;
    return true;
}

27、字符串比较(根据ASKII)

int StrCompare(String s,String t){
    for(int i = 1 ; i <= s.len && i <= t.len ; i++){
        if( s.data[i] != t.data[i] )//不等直接返回相减结果
            return s.data[i] - t.data[i];
    }
    return s.len - t.len; //前i个字符都相等,返回长度差
}

28、朴素模式匹配

​ 朴素匹配内容也在王道书上有详细解释,思想就是暴力求解,匹配到就一直往下匹配,不成功返回最开始匹配节点的下一个位置重新开始新一轮匹配,时间复杂度为 O(mn) m,n分别为两字符串长度。

int Index(String s,String t){
    itn i=1,j=1;
    while( i <= s.len && j <= t.len){
        if(s.data[i] == t.data[j]){
            i++;
            j++;
        }else{
            i=i-j+2; //i返回最开始匹配的下标加一,这里也是不要记,举个例子就推出来了
            j=1;
        }
    }
    if( j > t.len)
        return i-j+1;
    return -1;
}

29、KMP

​ 大名鼎鼎的KMP算法,时间复杂度O(m+n)。可以看出来与朴素模式匹配的差距很大,主要思想是利用next数组实现主串不回溯。 其代码主要区别就是在于j==0和匹配失败时候的差别,大体是完全一样的。

int Index(String s,String t,int next[]){
    itn i=1,j=1;
    while( i <= s.len && j <= t.len){
        if( j == 0 || s.data[i] == t.data[i]){
            i++;
            j++;
        }else{
            j=next[j];
        }
    }
    if( j > t.len)
        return i-j+1;
    return -1;
}

30、求next数组

​ next数组代码简单,但是原理很难用几句话解释,建议同学们自己推演一遍后,根据下面这个博主的图解就会对代码有清晰的认识,

硬记下来也不难哦。

【KMP算法之求next数组代码讲解】 【精准空降到 14:17】 https://www.bilibili.com/video/BV16X4y137qw/?share_source=copy_web&vd_source=d585976f52dd2035e51f2aac04e1b238&t=857

void Get_next(String s,int next[]){
    itn i=1,j=0;
    next[1] = 0;
    while( i <= s.len ){
        if( j == 0 || s.data[i] == s.data[j]){
            next[++i] = ++j;
        }else{
            j=next[j];
        }
    }
}

第五章 树

​ 恭喜到这的同学,即将打开数据结构世界的大门,理解后面这些基础代码其中的原理,你的数据结构就完全及格了!

31、二叉树的定义

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

32、二叉树的先序遍历

​ 访问顺序根左右。

void PreOrder(BiTree T){ //递归方法,这玩意一眼就会了对不对
	if (T){
		visit(T); //自定义访问树的方式
		PreOrder(T->lchild);
		PreOrder(T->rchild);
	}
}

void PreOrder2(BiTree Tree){ //非递归方法,考试最喜欢考了,看起来难,实际很简单就是用栈存储节点信息
	Stack s; //弄个栈出来,不记得栈就往上翻一翻哦
    Init(s);
    BiTNode *T = Tree; //防止指针误操作破坏原树结构
    while( T!= NULL || !IsEmpty(s)){
        if(T){
            visit(T);
            push(s,T);
            T=T->lchild;
        }else{
            pop(s,T);
            T=T->rchild;
        }
    }
}

BiTree CreateBiTree(){ //先序创建二叉树,快乐加餐,先序遍历的应用
	BiTree T;
    char ch;
	cin >> ch;
	if (ch == '#')
		T = NULL;
	else{
		T = new BiTNode;
 		T->data = ch;
 		T->lchild = CreateBiTree();
 		T->rchild = CreateBiTree();
 	}
 	return T;
 }

33、二叉树的中序遍历

​ 访问顺序左根右。

void InOrder(BiTree T){ //递归方法
	if (T){
		InOrder(T->lchild);
        visit(T); 
		InOrder(T->rchild);
	}
}

void InOrder2(BiTree Tree){ //非递归方法
	Stack s;
    Init(s);
    BiTNode *T = Tree;
    while( T!= NULL || !IsEmpty(s)){
        if(T){
            push(s,T);
            T=T->lchild;
        }else{
            pop(s,T);
            visit(T);
            T=T->rchild;
        }
    }
}

34、二叉树的后序遍历

​ 访问顺序左右根。

void PostOrder(BiTree T) {
    if (T) {
        PostOrder(T->lchild);
        PostOrder(T->rchild);
        visit(T);
    }
}

void PostOrder2(BiTree Tree){ //非递归方法,需要添加一个标识记录上一次访问的节点来判断右子树是否已经被访问过了
	Stack s;
    Init(s);
    BiTNode *T = Tree; 
    BiTNode *r = NULL; 
    while( T!= NULL || !IsEmpty(s)){
        if(T){
            push(s,T);
            T=T->lchild;
        }else{
            pop(s,T);
            if( T->rchild && r!= T->rchild){//判断右子树是否已经访问过了
                push(s,T);
                T = T->rchild;
            }else{
                visit(T);
            	r=T;
                T=NULL;//当访问完此节点时,也就代表着这个节点的子树已经全部访问完了,置空来访问上一层节点
            }
        }
    }
}

35、二叉树的层序遍历

​ 利用队列实现。

void LevelOrder(BiTree T) {
    Queue Q;             // 创建循环队列 Q 用于层序遍历
    InitQueue(Q);           // 初始化队列
    BiTree p;               // 定义一个二叉树结点指针 p
    EnQueue(Q, T);         // 将根节点 T 入队

    while (!QueueEmpty(Q)) {
        DeQueue(Q, p);     // 出队一个结点,并将其值输出
        visit(p)

        if (p->lchild != NULL)
            QueueIn(Q, p->lchild);   // 如果左子树非空,则将左子树入队

        if (p->rchild != NULL)
            QueueIn(Q, p->rchild);   // 如果右子树非空,则将右子树入队
    }
}

36、二叉树的深度

​ 递归很简单,我们也可以用层序遍历思想实现非递归算法,并且非递归算法可以利用来解决其他算法题,很方便,所以同学们也要熟练掌握非递归算法。

int treedepth(BiTree T){//递归
    if(T == NULL)
        return 0;
    else{
        int l = treedepth(t->lchild);
        int r = treedepth(t->rchild);
        return l>r?l+1:r+1;
    }
}

int treedepth1(BiTree T){//非递归,利用层序遍历思想,使用last记录每层最后一个元素位置,当遍历到last就代表层数+1
    if(T == NULL)
        return 0;
    int last = 0,lever=0;//last指向一层中最后一个节点,现在就默认指向了第一个节点,因为第一个节点入队时在队列中下标为0
    int frone = -1;
    int rear = -1;
    BiNode* Q[Max]; //队列,这里的队列不封装更容易用一些
    Q[++rear] = T; //第一个节点入队
    BiNode *p;
    while(front < rear){//队列不为空
        p = Q[++front]; //出队
        if(p->lchild) 
            Q[++rear] = p->lchild;
       	if(p->rchild)
            Q[++rear] = p->rchild;
        if(front == last){ //当出队的这个元素等于last本层最后一个节点,就代表层数+1并且下一层元素已经全部入队,此时的
            				//rear就是指向了下一层元素的最后一个节点
            level++;
            last = rear;
        }
    }
    return level;
    
}

37、表达树求值

int op(int a, char OP, int b) {//计算函数
    if (OP == '+') {
        return a + b;
    } else if (OP == '-') {
        return a - b;
    } else if (OP == '*') {
        return a * b;
    } else if (OP == '/') {
        if (b == 0) {
            cout << "b cannot be zero!";
            return 0;
        } else {
            return a / b;
        }
    }
}

int com(BiTree T) {
    int A, B;
    if (T != NULL) {
        if (T->lchild != NULL && T->rchild != NULL) {//当左右孩子都有时T为一个函数操作数,先递归调用左右子树求出的值再														//运算
            A = com(T->lchild);
            B = com(T->rchild);
            return op(A, T->data, B);
        } else {      
            return T->data - '0'; //叶子结点只要返回数字即可,char字符-'0'就是int类型,也可用ASKII码的方法 T->data-48
        }
    } else {
        return 0;
    }
}

38、在二叉排序树中查找值为 X 的结点,若找到,则记数(count)加 1;否则,作为一个新结点插入树中,插入后仍为二叉排序树,写出其非递归算法

void Search_X(BiTree &T, int X) {
    BiTNode *p, *f, *q;
    p = new BiTNode;    // 生成一个数据域为 X 的新结点 e
    p->data = X;
    p->lchild = p->rchild = NULL;
    p->count = 0;

    if (!T) {   // 如果该树为空,把P赋值给T
        T = p;
        return;
    }

    f = NULL; q = T;   // f 为当前结点的父亲结点,q 为当前结点

    while (q) {
        if (q->data == X) {
            q->count++;    // 如果找到该值则计数加 1
            return;
        }

        f = q;
        if (q->data > X) {
            q = q->lchild;
        } else {
            q = q->rchild;
        }
    }

    if (f->data > X) {   // 如果找不到,就将新结点插入树中
        f->lchild = p;
    } else {
        f->rchild = p;
    }
}

38、查找指定的值 key

void find(BiTree T, BiTree &q, char key) {
    if (T != NULL) {
        if (T->data == key) {
            q = T;
        } else {
            find(T->lchild, q, key);
            find(T->rchild, q, key);
        }
    }
}

39、查找前中后序中第 n 个值

​ 其实很简单,用之前的递归遍历算法改变visit函数内容即可。

以下只做先序示范。

int n = 0;

void find(BiTree T, int key) { //先序
    if (T != NULL) {
        ++n;
        if (n == key) {
            cout << T->data;
        }
        find(T->lchild, key);
        find(T->rchild, key);
    }
}

40、中序线索二叉树的创建

​ 线索二叉树很重要,同学们要牢记,以下只做中序示例。

​ 先序后序基本一样,先序要在左子树递归前做是否是子树的判断即T->ltag == 0否则会进入循环。

typedef struct ThreadNode {
    int data;
    struct ThreadNode *lchild, *rchild;
    int ltag, rtag; // 左右线索标志, 为 0 时表示孩子,为 1 时表示结点前驱(后继)
} ThreadNode, *ThreadTree;

// 通过中序遍历对二叉树线索化的递归算法
void InThread(ThreadTree &p, ThreadTree &pre) {
    if (p != NULL) {
        InThread(p->lchild, pre); // 递归,线索化左子树

        if (p->lchild == NULL) { // 左子树为空,建立前驱线索
            p->lchild = pre;
            p->ltag = 1;
        }

        if (pre != NULL && pre->rchild == NULL) { // 建立前驱结点的后继线索
            pre->rchild = p;
            pre->rtag = 1;
        }

        pre = p; // 标记当前结点称为刚刚访问过的结点
        InThread(p->rchild, pre); // 递归,线索化右子树
    }
}

// 通过中序遍历建立中序线索二叉树的主过程算法
void CreateInThread(ThreadTree &T) {
    ThreadTree pre = NULL;
    
    if (T != NULL) { // 非空二叉树,线索化
        InThread(T, pre); // 线索化二叉树
        if(pre->rchild == BULL){
            pre->rchild = NULL; // 处理遍历的最后一个结点
        	pre->rtag = 1;
        }
    }
}

41、中序线索二叉树的遍历

// 求中序线索二叉树中序序列下的第一个结点
ThreadNode *Firstnode(ThreadNode *p) {
    while (p->ltag == 0)
        p = p->lchild; // 最左下结点,不一定是叶结点
    return p;
}

// 求中序线索二叉树中结点 p 在中序序列下的后继
ThreadNode *Nextnode(ThreadNode *p) {
    if (p->rtag == 0)
        return Firstnode(p->rchild);
    else
        return p->rchild; // rtag==1 直接返回后继线索
}

// 不含头结点的中序线索二叉树的中序遍历
void Inorder(ThreadNode *T) {
    for (ThreadNode *p = Firstnode(T); p != NULL; p = Nextnode(p))
        cout << p->data << endl;
}

42、输出根结点到每个叶结点的路径

// 输出二叉树中所有从根结点到叶子结点的路径(递归)
void FindAllPath(BiTree pRoot, char *path, int pathlen) {
    if (pRoot != NULL) {
        path[pathlen++] = pRoot->data;

        if (pRoot->lchild == NULL && pRoot->rchild == NULL) { // 如果当前结点是叶子结点,输出路径
            for (int i = 0; i < pathlen; i++) {
                cout << path[i];
            }
            cout << endl;
        } else { // 否则递归处理左右子树
            FindAllPath(pRoot->lchild, path, pathlen);
            FindAllPath(pRoot->rchild, path, pathlen);
        }
    }
}

43、哈夫曼树建立

​ 小巨人来了,这里预备知识需要快排的代码,看起来多其实就是按照哈夫曼树生成的思想构建。

​ 首先初始化一个哈夫曼树以数组形式,并且将权值列表初始化,排序后先选取前两个节点的值合并生成新的节点,插入到哈夫曼数组中,再根据去掉两个节点和新加入的节点进行排序,重复步骤就可以了,具体请看代码。

// 哈夫曼树结点的定义
typedef struct HNode {
    int w;              // 结点的权值
    struct HNode *lchild, *rchild;  // 结点的左孩子和右孩子指针
} HNode, *HfmTree;

// 快速排序,按照结点的权值进行排序
void sort_w(HNode* ts[], int low, int high) {
    int i = low, j = high;
    HNode *s;

    if (i < j) {
        s = ts[i];
        while (i < j) {
            while (i < j && ts[j]->w >= s->w) j--;
            ts[i] = ts[j];
            while (i < j && ts[i]->w <= s->w) i++;
            ts[j] = ts[i];
        }
        ts[i] = s;
        sort_w(ts, low, i - 1);
        sort_w(ts, i + 1, high);
    }
}

// 创建哈夫曼树
HfmTree huffmanTreeCreate(int w[], int n) {
    HNode* ts[2 * n - 1];   // 哈夫曼树个数等于2 * n - 1
    int nownum = 0, treenum = 0;   // 记录现在拥有的树节点个数和哈夫曼树已经插入的个数,用来控制快排的起始下标(treenum)										//和终止下标(nownum-1)

    // 初始化哈夫曼树数组
    for (int i = 0; i < n; i++) {
        HNode* s = new HNode;
        s->w = w[i];
        s->lchild = s->rchild = NULL;
        ts[nownum++] = s;
    }

    // 对哈夫曼树数组进行快速排序
    sort_w(ts, 0, nownum - 1);

    // 合并结点构建哈夫曼树
    while (nownum - 1 != treenum) {  // 除根节点以外的节点都已经合并就结束
        HNode* p = ts[treenum++];
        HNode* q = ts[treenum++];
        HNode* s = new HNode;  //生成合并后的节点
        s->w = p->w + q->w;
        s->lchild = p;
        s->rchild = q;
        ts[nownum++] = s; //将生成的合并节点加入到数组中
        sort_w(ts, treenum, nownum - 1);
    }

    return ts[nownum - 1];  // 返回哈夫曼树的根节点
}

第六章 图

​ 重点重点重点,图涉及到很多算法的基础操作。

44、图的定义

// 图的邻接矩阵表示结构体
typedef struct {
    char vexs[MVNum];      // 顶点表,用于存储图中的顶点信息
    int arcs[MVNum][MVNum]; // 邻接矩阵,用于存储图中顶点之间的关系(边的权值)
    int vexnum;            // 顶点数目
    int arcnum;            // 边的数目
} AMGraph;

// 边表结点
typedef struct ArcNode {
    int adjvex;                // 该弧所指向的顶点的位置(邻接点域)
    struct ArcNode* nextArc;   // 指向下一条弧的指针
    int info;                  // 数据域
} ArcNode;

// 顶点表结点
typedef struct VNode {
    char data;              // 顶点信息
    ArcNode* firstArc;      // 指向第一条依附该顶点的弧的指针
} VNode;

// 邻接表表示邻接表类型
typedef struct {
    VNode vertices[MVNum];   // 邻接表
    int vexnum;              // 图的当前顶点数
    int arcnum;              // 图的当前边数
} ALGraph;

45、图的基础操作

// 返回顶点 v 的第一个邻接点
int FirstAdjVex(AMGraph G, int v) {// 邻接矩阵
    if (v < 0 || v >= G.vexnum)
        return -1;  // 输入顶点 v 不在图的顶点范围内,返回 -1

    for (int i = 0; i < G.vexnum; ++i) {
        if (G.arcs[v][i] == 1)
            return i;  // 找到 v 的第一个邻接点,返回邻接点的位置
    }

    return -1;  // 没有找到 v 的邻接点,返回 -1
}

int FirstAdjVex2(ALGraph mg, int v) {// 邻接表
    if (mg.vertices[v].firstArc != NULL)
        return mg.vertices[v].firstArc->adjvex;
    else
        return -1;  // 如果顶点 v 没有邻接点,返回 -1(或其他适当的值)
}

// 返回顶点 v 相对于顶点 w 的下一个邻接点
int NextAdjVex(AMGraph G, int v, int w) {// 邻接矩阵
    if (v < 0 || v >= G.vexnum || w < 0 || w >= G.vexnum)
        return -1;  // 输入顶点 v 或 w 不在图的顶点范围内,返回 -1

    for (int i = w + 1; i < G.vexnum; ++i) {
        if (G.arcs[v][i] == 1)
            return i;  // 找到 v 相对于 w 的下一个邻接点,返回邻接点的位置
    }

    return -1;  // 没有找到 v 相对于 w 的下一个邻接点,返回 -1
}

int NextAdjVex(ALGraph mg, int v, int w) {// 邻接表
    // 先找到 w 在 v 链中的位置
    ArcNode* p = mg.vertices[v].firstArc;

    while (p != NULL && p->adjvex != w)
        p = p->nextArc;

    if (p != NULL && p->nextArc != NULL)
        return p->nextArc->adjvex;  // 返回 w 在 v 链中的下一个邻接点的位置
    else
        return -1;  // 如果找不到下一个邻接点
}

46、BFS

// 广度优先搜索算法(Breadth-First Search)
void BFS(Graph mg, int x) { //用多态理解,这里无论邻接表还是矩阵都能用
    bool visited[100] = { false };  // 访问标记数组,初始化为 false
    Queue q;  // 定义一个循环队列
    visit(mg,x);  // 输出起始顶点的数据
    visited[x] = true;  // 标记起始顶点为已访问
    EnQueue(q, x);  // 将起始顶点入队
    int u = 0;
    while (EmptyQueue(q) == false) {  // 当队列非空时
        DeQueue(q, u);  // 出队一个顶点 u
        for (int w = FirstAdjVex(mg, u); w >= 0; w = NextAdjVex(mg, u, w)) {
            // 遍历顶点 u 的所有邻接点
            if (visited[w] == false) {
                visit(mg,w);  // 输出邻接点的数据
                visited[w] = true;  // 标记邻接点为已访问
                EnQueue(q, w);  // 将邻接点入队
            }
        }
    }
}

47、DFS

// 从顶点 v 出发递归的深度优先遍历连通图 G
void DFS(AMGraph G, int v) {
    visit(G,v);  // 输出顶点 v
    visited[v] = true;  // 置访问标志数组相应分量值为 true
    for (int w = FirstAdjVex(G, v); w >= 0; w = NextAdjVex(G, v, w)) {
        // 遍历顶点 v 的所有邻接点
        if (!visited[w])
            DFS(G, w);  // 对未访问的邻接点递归调用 DFS
    }
}

// 深度优先搜索遍历非连通图
void DFSTraverse(AMGraph G) {
    for (int i = 0; i < G.vexnum; i++)
        visited[i] = false;  // 访问标志数组初始化
    for (int j = 0; j < G.vexnum; j++) {
        if (!visited[j])  // 对尚未访问的顶点调用 DFS
            DFS(G, j);
    }
}

// 非递归深度优先搜索算法,此方法会导致节点重复入栈
void DFS_NO_RC(Graph g, int v) {
    Stack s;  // 定义一个栈
    Init(s);  // 初始化栈
    for (int i = 0; i < g.vexnum; i++)
        visited[i] = false;  // 访问标志数组初始化为 false
    push(s, v);  // 将起始顶点入栈

    while (!isEmpty(s)) {
        pop(s, v);  // 出栈一个顶点 v
        if (!visited[v]){
            visit(g, v);  // 访问顶点 v
        	visited[v] = true;  // 标记顶点 v 为已访问
        } 

        for (int w = FirstAdjVex(g, v); w >= 0; w = NextAdjVex(g, v, w)) {
            // 遍历顶点 v 的所有邻接点
            if (!visited[w])
                push(s, w);  // 将未访问的邻接点入栈
        }
    }
}

48、求不带权连通图中距点 v 的最远点

// 进行广度优先搜索(BFS)的函数
int BFS_v(Graph G, int v) {
    // 用数组记录每个顶点距离起始顶点的层次,初始值设为 -1
    int d[G.vexnum];
    for (int i = 0; i < G.vexnum; i++)
        d[i] = -1;
    
    // 距离起始顶点为 0
    d[v] = 0;

    // 初始化队列
    Queue Q;
    Init(Q);

    // 访问起始顶点
    visit(G, v);
    visited[v] = true;

    // 起始顶点入队
    EnQueue(Q, v);

    int u = 0;

    // BFS 主循环
    while (!isEmpty(Q)) {
        // 出队一个顶点 u
        DeQueue(Q, u);

        // 遍历 u 的邻接点
        for (int w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w)) {
            // 如果邻接点未被访问,则标记为已访问,输出其数据,并入队
            if (!visited[w]) {
                visited[w] = true;
                visit(G, w);
                d[w] = d[u] + 1;
                EnQueue(Q, w);
            }
        }
    }

    // 返回最后出队的顶点(一定是距离最远的)
    return u;
}

49、判断无向图是否是一棵树

// 从顶点 v 开始深度优先搜索,统计顶点数和边数
void DFS1(ALGraph G, int v, int &Vnum, int &Enum, bool *visited) {
    visited[v] = true; // 标记当前顶点为已访问
    ++Vnum; // 增加顶点数

    for (int w = FirstAdjVex(G, v); w >= 0; w = NextAdjVex(G, v, w)){
        ++Enum; // 增加边数

        // 如果邻接点未被访问,递归进行深度优先搜索
        if (!visited[w])
            DFS1(G, w, Vnum, Enum, visited);
    }
}

// 判断图是否为树
bool isTree(ALGraph G) {
    // 初始化访问标志数组
    bool visited[G.vexnum];
    for (int i = 0; i < G.vexnum; i++)
        visited[i] = false;

    int Vnum = 0, Enum = 0;
    
    // 从顶点 0 开始进行深度优先搜索
    DFS1(G, 0, Vnum, Enum, visited);

    // 判断是否为树:顶点数等于边数加 1
    if (Vnum == G.vexnum && Enum == 2 * (Vnum - 1))
        return true;

    return false;
}

50、分别用 BFS 和 DFS 判断有向图中 i,j 是否连通

bool visited[Max];
// 判断从顶点 i 到顶点 j 是否存在路径
int BFSTrace(ALGraph G, int i, int j) {
    Queue Q;
    Init(Q);

    // 初始化访问标志数组
    for (int k = 0; k < G.vexnum; k++)
        visited[k] = false;

    EnQueue(Q, i); // 将起始顶点 i 入队
    visited[i] = true;
    int u = 0;

    while (!EmptyQueue(Q)) {
        DeQueue(Q, u);

        // 遍历 u 的邻接点
        for (int w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w)) {
            // 如果邻接点未被访问
            if (!visited[w]) {
                // 如果找到目标顶点 j,返回 1 表示存在路径
                if (j == w)
                    return 1;

                visited[w] = true;
                EnQueue(Q, w);
            }
        }
    }

    // 若遍历完整个图都没有找到目标顶点 j,返回 0 表示不存在路径
    return 0;
}

// 基于深度优先搜索判断有向图 G 中顶点 i 到顶点 j 是否有路径
// 若存在路径则返回 1,否则返回 0
int PathDFS(ALGraph G, int i, int j) {
    // 若起始点和目标点相同,存在路径
    if (i == j)
        return 1;

    // 标记当前顶点 i 为已访问
    visited[i] = true;

    // 遍历 i 的邻接点
    for (int w = FirstAdjVex(G, i); w >= 0; w = NextAdjVex(G, i, w)) {
        // 如果邻接点未被访问且存在路径
        if (!visited[w] && PathDFS(G, w, j))
            return 1;
    }

    // 如果遍历完 i 的所有邻接点都没有找到路径,返回 0 表示不存在路径
    return 0;
}

51、Prim 算法(可用于找最小连通图,最小生成树)

// 使用 Prim 算法求最小生成树
void Prim(AMGraph g, int v, int &sum) {
    // visited[i] 为顶点 i 是否被访问过;lowcost[i] 为顶点 v 到顶点 i 的最小代价
    // min 为当前最小权值和
    int visited[MAX], u, lowcost[MAX], min;

    // 初始化各元素的值
    for (int i = 0; i < g.vexnum; ++i) {
        visited[i] = 0;
        lowcost[i] = g.arcs[v][i];
    }

    visited[v] = 1;
    sum = 0;

    for (int i = 0; i < g.vexnum - 1; ++i) { // 剩余 n-1 个顶点
        min = MAXInt;

        // 寻找与当前集合中顶点相连的最小权值和的顶点
        for (int j = 0; j < g.vexnum; ++j) {
            if (visited[j] == 0 && lowcost[j] < min) {
                min = lowcost[j];
                u = j;
            }
        }

        visited[u] = 1;
        sum += min;

        // 更新矩阵的值
        for (int j = 0; j < g.vexnum; ++j) {
            if (visited[j] == 0 && g.arcs[u][j] < lowcost[j]) {
                lowcost[j] = g.arcs[u][j];
            }
        }
    }
}

52、Kruskal 算法(可用于找最小连通图,最小生成树)

​ 这里用到了并查集和快排来实现Kruskal 算法。

const int MAX = 100; // 假设最大顶点数为 100

typedef struct Road {
    int a, b, w; // 边的顶点 a, b, 权值 w
} Road;

Road road[MAX]; // 边的集合
int v[MAX]; // 并查集

int getRoot(int a) { // 找到结点 a 的根结点
    while (a != v[a])
        a = v[a];
    return a;
}

void QuickSort(Road road[], int low, int high) {
    int i = low, j = high;
    Road temp;

    if (low < high) {
        temp = road[low];
        while (i < j) {
            while (i < j && road[j].w > temp.w)
                j--;

            if (i < j) {
                road[i] = road[j];
                ++i;
            }

            while (i < j && road[i].w < temp.w)
                ++i;

            if (i < j) {
                road[j] = road[i];
                j--;
            }
        }
        road[i] = temp;
        QuickSort(road, low, i - 1);
        QuickSort(road, i + 1, high);
    }
}

void Kruskal(AMGraph g, Road road[], int &sum) {
    for (int i = 0; i < g.vexnum; ++i)
        v[i] = i;

    QuickSort(road, 0, g.arcnum - 1);

    for (int i = 0; i < g.arcnum; ++i) {
        int a = getRoot(road[i].a);
        int b = getRoot(road[i].b);

        if (a != b) { //不在一个集合就加入
            v[a] = b;
            sum += road[i].w;
        }
    }
}

53、Dijkstra 算法

​ 和prim算法类似,多了path数组记录路径上本节点的上一个节点。

const int MAX = 100; // 假设最大顶点数为 100
const int MAXInt = 10000; // 假设最大权值为 10000

const int VMAX = 100; // 假设最大顶点数为 100

// Dijkstra 最短路径算法
void Dijkstra(int v, int path[], int dist[], AMGraph G) {
    int u, vset[VMAX], min;

    // 初始化距离数组和路径数组
    for (int i = 0; i < G.vexnum; ++i) {
        vset[i] = 0;
        dist[i] = G.arcs[v][i];
        if (dist[i] < MAXInt)
            path[i] = v;
        else
            path[i] = -1;
    }

    vset[v] = 1;
    path[v] = -1;

    // 主循环,每次找到一个离起点最近的顶点加入最短路径集合
    for (int i = 0; i < G.vexnum - 1; ++i) {
        min = MAXInt;

        // 在未加入最短路径集合的顶点中找到距离起点最近的顶点 u
        for (int j = 0; j < G.vexnum; ++j)
            if (vset[j] == 0 && dist[j] < min) {
                min = dist[j];
                u = j;
            }

        vset[u] = 1;

        // 更新当前最短路径集合中的顶点到其他顶点的距离
        for (int j = 0; j < G.vexnum; ++j) {
            if (vset[j] == 0 && dist[u] + G.arcs[u][j] < dist[j]) {
                dist[j] = dist[u] + G.arcs[u][j];
                path[j] = u;
            }
        }
    }
}

// 回溯路径,输出最短路径上的顶点
void traceBack(int path[], int i, int j) {
    if (j == -1)
        return;

    traceBack(path, i, path[j]);
    std::cout << j << " ";
}

54、Floyd 算法

// Floyd 最短路径算法
void Floyd(AMGraph g, int path[][MVNum], int A[]) {
    // 初始化距离矩阵和路径矩阵
    for (int i = 0; i < g.vexnum; ++i)
        for (int j = 0; j < g.vexnum; ++j) {
            A[i][j] = g.arcs[i][j];
            path[i][j] = -1;
        }

    // 主循环,遍历所有中间顶点,更新距离矩阵和路径矩阵
    for (int k = 0; k < g.vexnum; ++k)
        for (int i = 0; i < g.vexnum; ++i)
            for (int j = 0; j < g.vexnum; ++j)
                if (A[i][j] > A[i][k] + A[k][j]) {
                    A[i][j] = A[i][k] + A[k][j];
                    path[i][j] = k;
                }
}

// 回溯路径,输出最短路径上的顶点
void traceBack2(int i, int j, int path[][MVNum], int A[]) {
    if (A[i][j] == MAXInt)
        return;

    if (path[i][j] == -1)
        cout << "<" << i << "," << j << ">";
    else {
        traceBack2(i, path[i][j], path, A);
        traceBack2(path[i][j], j, path, A);
    }
}

55、拓扑排序

int indegree[MVNum]; // 存放各顶点入度
int topo[MVNum]; // 存放拓扑序列的顶点序号

// 求各顶点的入度
void FindInDegree(ALGraph G, int indegree[]) {
    for (int i = 0; i < G.vexnum; i++)
        indegree[i] = 0;

    // 遍历整个邻接表求出入度,顶点入度即为邻接表边结点中一共所出现的次数
    for (int j = 0; j < G.vexnum; j++) {
        for (int w = FirstAdjVex(G, j); w >= 0; w = NextAdjVex(G, j, w))
            indegree[w]++;
    }
}

// 拓扑排序
int TopologicalSort(ALGraph G, int *topo) {
    Stack S; // 存入度为 0 的顶点
    Init(S);
    int i, w = 0, m = 0; // m 用来计数

    FindInDegree(G, indegree); // 求各顶点的入度存入 indegree 数组

    // 将入度为 0 的顶点入栈
    for (i = 0; i < G.vexnum; i++)
        if (!indegree[i])
            PushStack(S, i);

    while (S.top >= 0) {
        PopStack(S, i);
        topo[m] = i; // 入度为 0 的出栈并存入 topo 数组中
        m++; // 计数加一

        // 遍历当前顶点的邻接点
        for (int w = FirstAdjVex(G, i); w >= 0; w = NextAdjVex(G, i, w)) {
            indegree[w]--; // 顶点的入度减 1 来代替删除边的操作
            if (indegree[w] == 0) // 如果减 1 后为 0 则入栈
                PushStack(S, w);
        }
    }

    if (m < G.vexnum)
        return 0; // 输出的顶点数小于有向图中的顶点数,则说明有向图中存在环
    else
        return m; // 否则拓扑排序成功,无环
}

第七章 查找

胜利就在眼前,顺序查找我直接略过了,放这有点侮辱人。

56、折半查找法(二分查找)

// 二分查找递归算法
int BinarySearchRecursive(int arr[], int low, int high, int target) {
    if (low <= high) {
        int mid = low + (high - low) / 2;

        if (arr[mid] == target)
            return mid; // 找到目标值,返回索引
        else if (arr[mid] < target)
            return BinarySearchRecursive(arr, mid + 1, high, target); // 在右侧继续查找
        else
            return BinarySearchRecursive(arr, low, mid - 1, target); // 在左侧继续查找
    }

    return -1; // 目标值不存在
}

// 二分查找非递归算法
int BinarySearchIterative(int arr[], int low, int high, int target) {
    while (low <= high) {
        int mid = low + (high - low) / 2;

        if (arr[mid] == target)
            return mid; // 找到目标值,返回索引
        else if (arr[mid] < target)
            low = mid + 1; // 在右侧继续查找
        else
            high = mid - 1; // 在左侧继续查找
    }

    return -1; // 目标值不存在
}

57、二叉排序树查找和插入

typedef struct BiTNode {
    int data;       // 结点值
    int b;          // 平衡因子(在此代码中未完全实现)
    struct BiTNode *lchild, *rchild;  // 左子树和右子树指针
    int count;      // 以该结点为根的子树结点数
} BiTNode, *BiTree;

BiTNode* BST_Search(BiTree T, int key) {
    // 如果当前子树为空,则未找到关键字
    if (!T)
        return NULL;

    // 如果当前结点的数据等于关键字,则返回该结点
    if (T->data == key)
        return T;

    // 如果关键字小于当前结点的数据,则在左子树中查找
    else if (T->data > key)
        return BST_Search(T->lchild, key);

    // 如果关键字大于当前结点的数据,则在右子树中查找
    else
        return BST_Search(T->rchild, key);
}

int BST_Insert(BiTree &T, int key) {
    // 如果原树为空,新插入的记录为根结点
    if (T == NULL) {
        T = new BiTNode;  // 分配新结点
        T->data = key;
        T->lchild = T->rchild = NULL;
        return 1;  // 返回1,插入成功
    }else if (T->data == key) // 如果关键字等于当前结点的数据,返回0表示插入失败
        return 0;
    else if (key < T->data) // 如果关键字小于当前结点的数据,向左子树插入
        return BST_Insert(T->lchild, key);
    else // 否则,向右子树插入
        return BST_Insert(T->rchild, key);
}

58、平衡二叉树的高

int Height(BiTree T) {//非递归
    int level = 0;  // 记录树的高度
    BiTNode *p;
    p = T;
    while (p) {
        level++;  // 树的高度增1
        // 如果平衡因子b为-1,沿右分支向下
        if (p->b < 0)
            p = p->rchild;
        // 如果平衡因子b大于等于0,沿左分支向下
        else
            p = p->lchild;
    }
    return level;  // 返回平衡二叉树的高度
}

int BalancedTreeHeight(BiTree T) {//递归
    if (!T)
        return 0;
    // 返回较大的子树高度加1
    return (T.b > 0) ? (BalancedTreeHeight(T->lchild) + 1) : (BalancedTreeHeight(T->rchild) + 1);
}

第八章 排序

59、直接插入排序(n^2,稳定)

void InsertSort(int A[], int n) {
    // 数组 A 中存放待排序序列,A[0]为哨兵
    int i, j;
    for (i = 2; i <= n; i++) {
        // 依次将 A[2]~A[n]插入到前面已经排好序的序列里
        if (A[i] < A[i - 1]) {
            // 若 A[i]关键码小于其前驱,将 A[i]插入有序表
            A[0] = A[i];  // 复制为哨兵,A[0]不存放元素
            for (j = i - 1; A[0] < A[j]; --j) {
                // 从后往前查找待插入位置
                A[j + 1] = A[j];  // 向后挪位
            }
            A[j + 1] = A[0];  // 赋值到插入位置
        }
    }
}

60、折半插入排序(n^2,稳定)

void InsertSort(int *A, int n) {
    int i, j, low, mid, high;
    for (i = 2; i <= n; i++) {
        // 依次将 A[2]~A[n]插入到前面已经排好序的序列里
        A[0] = A[i];  // 将 A[i] 暂存到 A[0]
        low = 1;
        high = i - 1;  // 设置折半查找的范围
        while (low <= high) {
            mid = (low + high) / 2;
            if (A[mid] > A[0])
                high = mid - 1;
            else
                low = mid + 1;
        }
        for (j = i - 1; j >= high + 1; --j)
            A[j + 1] = A[j];  // 统一后移元素,空出插入位置
        A[high + 1] = A[0];  // 插入操作
    }
}

61、希尔排序(缩小增量排序,时间复杂度未知,比插入块,不稳定)

void ShellSort(int A[], int n) {
    // A[0] 只是暂存单元,不是哨兵,当 j <= 0 时,插入位置已到
    int dk = 0, i = 0, j = 0;
    for (dk = n / 2; dk >= 1; dk = dk / 2) {  // 步长变化
        for (i = dk + 1; i <= n; i++) {
            if (A[i] < A[i - dk]) {  // 将 A[i] 插入有序增量子表
                A[0] = A[i];         // 暂存在 A[0]
                for (j = i - dk; j > 0 && A[0] < A[j]; j -= dk)
                    A[j + dk] = A[j];  // 记录后移,寻找插入位置
                A[j + dk] = A[0];      // 插入
            }
        }
    }
}

62、冒泡排序(n^2,稳定)

void BubbleSort(int A[], int n) {
    for (int i = 0; i < n - 1; ++i) {
        bool flag = false;  // 表示本趟排序是否发生交换的标志
        for (int j = 0; j < n - i - 1; ++j) {
            if (A[j] > A[j + 1]) {
                // 交换 A[j] 和 A[j + 1]
                int temp = A[j];
                A[j] = A[j + 1];
                A[j + 1] = temp;

                flag = true;  // 设置交换标志
            }
        }
        if (flag == false)
            return;  // 本趟遍历没有发生交换,说明表中已经有序
    }
}

63、快速排序(nlogn,不稳定)

void QuickSort(int A[], int low, int high) {
    if (low < high) {
        int pivot = A[low]; // 选择第一个元素作为基准元素
        int left = low, right = high;

        while (left < right) {
            while (left < right && A[right] >= pivot)
                --right;
            A[left] = A[right]; // 将比基准元素小的移到低端

            while (left < right && A[left] <= pivot)
                ++left;
            A[right] = A[left]; // 将比基准元素大的移到高端
        }

        A[left] = pivot; // 基准元素就位

        QuickSort(A, low, left - 1); // 对基准元素左边的子序列进行快速排序
        QuickSort(A, left + 1, high); // 对基准元素右边的子序列进行快速排序
    }
}

64、选择排序(n^2,不稳定)

void SelectionSort(int A[], int n) {
    for (int i = 0; i < n - 1; ++i) {
        int minIndex = i; // 记录最小元素的索引

        // 在未排序的部分中找到最小元素的索引
        for (int j = i + 1; j < n; ++j) {
            if (A[j] < A[minIndex]) {
                minIndex = j;
            }
        }

        // 将最小元素与当前位置交换
        if (minIndex != i) {
            int temp = A[i];
            A[i] = A[minIndex];
            A[minIndex] = temp;
        }
    }
}

65、堆排序(nlogn,不稳定)

// 调整以i为根的堆,使其满足堆的性质
void AdjustHeap(int A[], int i, int size) {
    int lchild = 2 * i + 1; // 左孩子索引
    int rchild = 2 * i + 2; // 右孩子索引
    int maxIndex = i;       // 初始化最大值索引为当前结点

    // 找到左右孩子中较大的值
    if (lchild < size && A[lchild] > A[maxIndex]) {
        maxIndex = lchild;
    }
    if (rchild < size && A[rchild] > A[maxIndex]) {
        maxIndex = rchild;
    }

    // 如果最大值索引不等于当前结点,交换它们,并递归调整交换后的子堆
    if (maxIndex != i) {
        swap(A[i], A[maxIndex]);
        AdjustHeap(A, maxIndex, size);
    }
}

// 堆排序
void HeapSort(int A[], int n) {
    // 构建最大堆,从最后一个非叶子结点开始向上调整
    for (int i = n / 2 - 1; i >= 0; --i) {
        AdjustHeap(A, i, n);
    }

    // 不断将最大值放到堆末尾,并重新调整堆
    for (int i = n - 1; i > 0; --i) {
        swap(A[0], A[i]);   // 将最大值放到数组末尾
        AdjustHeap(A, 0, i); // 重新调整堆
    }
}

66、归并排序(nlogn,稳定)

// 归并操作:合并两个有序数组为一个有序数组
void Merge(int A[], int low, int mid, int high) {
    int i = low, j = mid + 1;
    int* temp = new int[high - low + 1]; // 临时数组用于存放合并后的有序序列
    int k = 0;

    // 合并两个有序数组
    while (i <= mid && j <= high) {
        if (A[i] <= A[j])
            temp[k++] = A[i++];
        else
            temp[k++] = A[j++];
    }

    // 处理剩余元素
    while (i <= mid)
        temp[k++] = A[i++];
    while (j <= high)
        temp[k++] = A[j++];

    // 将合并后的有序序列复制回原数组
    for (i = low, k = 0; i <= high; i++, k++)
        A[i] = temp[k];

    // 释放临时数组的内存
    delete[] temp;
}

// 归并排序
void MergeSort(int R[], int low, int high) {
    if (low < high) {
        int mid = (low + high) / 2; // 计算中间位置
        MergeSort(R, low, mid);     // 对左半部分递归排序
        MergeSort(R, mid + 1, high); // 对右半部分递归排序
        Merge(R, low, mid, high);    // 合并左右两部分
    }
}

后话

​ 当你能完整默写出这66个代码,并且理解里面的算法思想,同学,你的实力已经可以秒杀王道数据结构里的百分之95的题目了。

​ 如果你还不能完全理解和完整背诵默写,那么继续加油,你的努力总会得到回报的,祝各位同学都能有美好前途和光明的未来!

​ 对spring源码感兴趣的小伙伴可以查看我的另一篇博客,考试结束我会持续更新,以后后续还会出基础算法的入门代码总结,带你进入算法的快乐天堂!

​ 这是我的spring源码入门教学博客http://t.csdnimg.cn/lcWtx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值