数据结构 && C笔记(考研版)

【前言】

只是一个考研党学习过程中做的笔记(主要是我自己看的),如有错误直接指出就好,不要骂我不要骂我不要骂我இ௰இ


【C语言】

1、Hello World!

#include <stdio.h>//这里是引入打印的头文件(等价于java中的import xxxx)

void main(){
//printf()打印的函数 等价于java中的System.out.printf()
    printf("Hello World!");
}

2、基本数据类型与变量

short——短整型(2B)

int——整形(4B)

long——长整型(8B)

long long——长长整型(8B)

float——浮点型(4B)

double——双精度浮点型(8B)

char——字符型(1B)

【注】与java不同

        ① C没有boolean类型,可以用int型来代替,0-false,1-true

        ② C没有String类型,字符串常用char[ ]来接收字符串

当定义一个变量时,计算机会在内存中分配出对应的连续地址以及内存空间。

3、指针

指针就是对象存放在内存中的地址

#include <stdio.h>

void main(){

    int a = 30;
    int b = 50;
    int c;

    int *p, *q;//定义两个指针变量
    
    p = &a;//&的作用是取a的地址,把a的地址赋给指针p
    q = &b;//把b的地址赋给指针q

    c = *p;//*p的意思在这里并不是指针的的意思了,而是取指针p所指向的地址的中的元素
           //由于p指针指向的是存放a的内存地址,所以*p就是30,把30赋给c,故c就是30
}

4、结构体

简单数据类型int,double.....描述一些基本的个体时是非常适用的,但当描述一些复杂的个体的时候,就没办法很好的描述出来了,比如一个学生(学号,成绩...),这时候就需要用到结构体。

(类似与Java中的实体类Bean)

//定义一个Student结构体
struct Student{
    int Sno;
    double score;
};
//定义一个变量
  Struct Student xiaohong;


//上面的写法比较麻烦,通常会使用到typedef来重新起一个别名
typedef struct Student{
    int Sno;
    double score;
}Stu;
//定义一个变量
  Stu xiaohong;

 typedef:为一种数据类型定义一个别名

【例】typedef  原名  别名

typedef int yoho;  //为int起了一个yoho别名
void main(){
    yoho i = 0;  //定义一个yoho(int)变量
}

5、数组

有序排列的用一类数据元素的集合。

//定义一个int型的数组,里面有5个元素,计算机也会分配连续的5个空间
int a[5];

a[0] = 30;       //赋值操作
a[1] = 70;
a[2] = 40;

int b[] = {80,70,60,50};
int c[5] = {80,70,60,50,40};

//也可以根据上面的结构体来创建一个数组
typedef struct Student{
    int Sno;
    int score;
}Stu;

Stu a[10];    //定义一个空间为10的数组
a[0].Sno = 10110;   //取结构体里面的元素进行赋值
a[0].score = 99;


//第二种创建数组的方式,动态创建
int *p;
p = (int *)malloc(sizeof(int)*5); //指针p指向这片空间的首地址

p[0] = 5;
p[1] = 4;

malloc:申请一片连续的空间

【注】用malloc函数,头部文件要加上 #include<stdlib.h>,也需要用free函数把这片内存释放掉。

6、函数定义

编程的过程中,经常会重复一个操作(比如交换两个元素位置...),用多了就会很麻烦,这时,就可以把交换元素的操作给封装成一个函数方法,之后在main函数中,直接调用交换函数方法就可以完成交换操作了。

void Swap(int &a, int &b){  //C++中的&a,&b形参,C语言不是这么写,类似着java来看
    int temp;              //void是空类型,故函数中的return可写可不写
    temp = a;               //当然也可以定义int型等等,但是要注意放回的类型,例如return 0;
    a = b;                  
    b = temp;
    
    return;
}

7、C语言中的参数传递和C++中的引用

①  值传递

/*
值传递——main函数的a,b只会在swap函数中运行,
但是结果并不会带回到main函数中
*/
void swap(int a, int b){
    int temp;
    temp = a;
    a = b;
    b = temp;
}

void main(){
    swap(x,y);
}

② 地址传递

/*
地址传递——main函数的a,b地址被swap函数的指针a,b指着,
*a,*b取到函数所指向的地址的元素进行操作,故修改了main函数x,y的结果
*/
void swap(int *a, int *b){
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
    return;
}

void main(){
    swap(&x,&y);    //注意这里加上了&,把操作结果带回来
}

③ C++中的引用

/*
C++常用的引用——仅仅用于C++中,&a意思是取了main函数中的a的地址,
在swap函数中操作会影响到main函数x,y的结果
*/
void swap(&a,&b){
    int temp;
    temp = a;
    a = b;
    b = temp;
}

void main(){
    swap(x,y);    //很像java,根据java的来理解
}

【顺序表】

1、顺序表的结构体定义

//静态定义一个顺序表

#define InitSize 100    //表长度的初始定义

typedef struct {
    int data[InitSize];    //固定大小的100
    int length;        //顺序表的当前长度
}SeqList;

//动态定义一个顺序表
typedef struct {
    int* data;     //指示动态分配数组的指针
    int length;     //顺序表的当前长度
    int MaxSize;    //数组的最大容量
}SeqList;       //动态分配数组顺序表的类型定义

2、顺序表的遍历及输出

//遍历操作
void ListVisit(SeqList L){
    for(int i=0; i<L.length; i++){
        printf("%d", L.data[i]);
    }
}

3、顺序表的查找

//按值查找
int Search(SeqList L, int e){
    for(int k=0; k<L.length; k++){  //同样需要遍历数组,一个个比对
        if(L.data[k] == e){
            return k;
        }return -1;
    }
}
//按位查找
int GetElem(SeqList L, int i){  //i是要查找的第几位元素,长度比下标大1
    if(i>=1 && i<L.length){
        return L.data[i-1];     //i-1即length长度-1=下标
    }return -1;
}

4、顺序表的逆置

//逆置表中的元素
void Reverse(SeqList *L){
    int temp;
    for(int i=0; i<L->length/2; i++){
        temp = L->data[i];
        L->data[i] = L->data[L->length-1-i];
        L->data[L->length-1-i] = temp;
    }
}

【注】L->data[ ] 就是C++中的L.data[ ] 的取值操作,因为需要把结构带回到main函数中,所以用了地址传递,在写函数的时候,注意看需不要把结果带回到main函数中来判断用值传递还是地址传递的办法,比如查找只需要进行查找的操作,并不需要修改表的数据元素,逆置则需要操作后把结果带回main函数。

5、顺序表中第i位置插入元素e

/*
*插入新元素,不合法则返回false,
*否则将第i个元素及其后面的所有元素依次往后移动一个位置,腾出空间,
*插入新元素e,
*顺序表长+1,
*插入成功返回true
*/
int ListInsert(SeqList *L, int i, int e){   //第i位置插入元素e
    if(i<1 || i>L->length+1){   //length+1是因为数组的后面+1也可以插入,但顺序表要相邻的,所以后面+2不能插入,除非后面+1有元素了
        return 0;
    }
    if(L->MaxSize == L->length){  //顺序表满了的话也是无法插入的
        return 0;
    }
    for(int j=L->length; j>=i; j--){    //从后面往前遍历,遍历到的往后移动,到第i位移动完停止
        L->data[j] = L->data[j-1];      //j-1为下标
        L->length++;
        return 1;
    }
}

6、有序顺序表插入元素x后依然有序

/*
 * 已知一个顺序表L,其中的元素递增有序排列,设计一个算法,
 * 插入一个int型元素x后保持该顺序表仍然递增有序排列,
 * 假设插入操作肯定成功,插入成功后返回插入元素所在位置。
*/
int ListInsert2(SeqList *L,int x){  //会改变原来的结构要用引用,int x要插入的新元素
    int i;  //不在for循环里面定义是因为最后还要return,在for循环里推出后,i就没了
    for(i=0; i<L->length; i++){     //遍历操作
        if(L->data[i] > x){     //一个个遍历,如果这个数组里的值大于新插入的x则跳出循环
            break;              //目的是训练i,此时i++多操作了一下,i是插入时的位置了,不是下标了
        }
    }
    for(int j=L->length; j>i; j--){    //遍历操作,把第i位后面的元素都往后移
        L->data[j] = L->data[j-1];
        L->data[i] = x;
        L->length++;       //插入后,长度也要增加
    }
    return i;
}

7、顺序表中删除第i个位置的元素

/*
 * 删除顺序表L中第i个位置的元素,若i的输入不合法,则返回false;
 * 否则将被删元素赋给引用变量e,
 * 并将第i+1个元素及其后面的所有元素一次往前移一个位置,返回true
 */
int ListDelete(SeqList *L, int i, int *e){
    if(i<1 || i>L->length){     //删除的第i位如果越界也是不可以的
        return -1;
    }
    e = L->data[i-1];   //第i个元素就是下标为i-1的元素
    for(int j=i; j<L->length; j++){
        L->data[j-1] = L->data[j]; //第一轮时i=j,j的位置空着,需要后面的元素前移,所以第j位的下标为j-1
    }
    L->length--;    //删除元素后,长度减1
    return 1;
}

8、顺序表中删除最小值

/*
 * 从顺序表中删除具有最小值的元素并由函数返回被删元素的值。
 * (假设)顺序表中数据元素全为正值且最小值唯一。
 */
int Del_Min(SeqList *L){
    if(L->length == 0){
        return -1;  //表的长度为0,说明没有元素
    }
    int min = L->data[0];   //假设数组第一个(下标0)为最小值先
    int pos = 0;    //定义一个pos记录下标
    for(int i=1; i<L->length; i++){    //直接从第二个(下标1)开始和最小值(上面假设了)对比
        if(L->data[i] < min){       //如果它比最小值还小,重新赋值给min
            min = L->data[i];
            pos = i;    //下标也重新更新一下
        }
    }
    //找到最小值和下标后,需要删除元素,后面的元素前移到下标pos的位置
    for(int j=pos; j<L->length-1; j++){     //直接从下标pos开始,后面元素往前移动,循环到哪里停止,画图具体分析一下
        L->data[j] = L->data[j+1];
    }
    L->length--;    //删除元素,长度减一
    return min;
}

9、顺序表中删除所有值为x的元素

/*
 * 对长度为n的顺序表L,编写一个时间复杂度为O(n)、空间复杂度为O(1)的算法,
 * 该算法删除线性表中所有值为x的数据元素
 */
void Del_x_1(SeqList *L, int x){   
    int k = 0;  //用来记录删除了几个元素
    for(int i=0; i<L->length; i++){ //遍历操作,对数组扫描一遍
        if(L->data[i] == x){
            k++;    //如果下标为i的元素=x,那么给k记录一票
        }else{
            L->data[i-k] = L->data[i];  //根据这个元素的前面删了k个元素,那么这个就要往前移动k位
        }
    }
    L->length = L->length-k;    //重新算顺序表的长度
}

//方法二,符合条件就拉去排队
void Del_x_2(SeqList *L, int x){
    int j = 0;  //排队的队列,覆盖旧的数组,新数组的下标
    for(int i=0; i<L->length; i++){
        if(L->data[i] != x){    //如果不是要删除的元素x,就拉去排队
            L->data[j] = L->data[i];    //拉去排队操作
            j++;    //队伍下一个
        }
    }
    L->length = j;  //虽然j是下标,但是第j位并没有数值,因为多操作了一下,因此j可以是长度
}

10、顺序表中删除所有在s和t之间的元素

/*
 * 从顺序表中删除其值在给定值s与t之间(包含s,t,要求s<t)的所有元素,
 * 若s或t不合理或顺序表为空,返回false,若执行成功返回true
 */
int Del_s_t_1(SeqList *L, int s, int t){    //此方法跟上面的方法1一样思路
    if(L->length == 0 || s >= t){
        return -1;
    }
    int k = 0;      //记录删除了几个元素
    for(int i = 0; i<L->length; i++){
        if(L->data >= s && L->data[i]<t){
            k++;    //符合条件的给它记一票
        }else{
            L->data[i-k] = L->data[i];     //前面删除了几个元素就要往前移动多少位
        }
    }
    L->length = L->length-k;    //表的长度要重新算
    return 1;
}

//方法二(排队法)
int Del_s_t_2(SeqList *L, int s, int t){    //此方法跟上面的方法2一样思路
    if(L->length == 0 || s >= t){
        return -1;
    }
    int j = 0;  //排队的队列,覆盖旧的数组,新数组的下标
    for(int i=0; i<L->length; i++){
        if(L->data[i] < s || L->data[i] > t){   //如果不是要删除的元素x,就拉去排队
            L->data[j] = L->data[i];    //拉去排队操作
            j++;    //队伍下一个
        }
    }
    L->length = j;  //虽然j是下标,但是第j位并没有数值,因为多操作了一下,因此j可以是长度
    return 1;
}

11、有序顺序表中删除所有值重复的元素

/*
 * 从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同。
 */
void Del_Same(SeqList *L){      //一样是上面方法2的排队法
    int j = 1;  //排队队列从1开始,因为下标为0的元素默认不重复不会被删除
    for(int i=1; i<L->length; i++){
        if(L->data[i] != L->data[j-1]){ //不重复的拉去排队,由于是有序顺序表,所以只需要跟前面的比较,不等于就拉去排队,数组举例112223344
            L->data[j] = L->data[i];   
            j++;
        }
    }
    L->length = j;  //数组长度
}

12、有序顺序表中删除所有值在s与t之间的元素

/*
 * 从有序顺序表中删除其值在给定值s与t之间(包含s,t,要求s<t)的所有元素,
 * 若s或t不合理或顺序表为空,返回false,若执行成功返回true
 */
int Del_s_t(SeqList *L, int s, int t){
    if(s >= t || L->length == 0){   //s>t,或者数组长度为0,都返回false
        return -1;
    }
    int i,j;    //用j去找第一个要删除的元素 用i去找第一个要保留的元素,即值t的后一位
    for(j=0; j<L->length && L->data[j] < s; j++);    //j从下标0开始遍历到下标j,下标j的值等于给定数值s停止
    if(j == L->length){     //越界也会false
        return -1;
    }
    for(i=j; i<L->length && L->data[i]<=t; i++);  //直接从第j位开始遍历,遍历到数值t,此时i++,第i位是第一个要保留的数值元素了
    for( ; i<L->length; i++,j++){
        L->data[j] = L->data[i];    //i位移到j位
    }
    L->length = j;  //数组长度重新计算
    return 1;
}

13、两个有序顺序表A和B合并成一个新的有序顺序表C

/*
 * 将两个有序顺序表A和B合并成一个新的有序顺序表C,若合并成功返回true,合并失败返回false
 */
int Merge(SeqList A, SeqList B, SeqList *C){    //顺序表A,B不用改表,改的是顺序表C,才要引用
    if(A.length + B.length > C->MaxSize){   //看C表能不能装的下
        return -1;
    }
    int i=0, j=0, k=0;  //定义三个下标i j k分别是顺序表A B C的
    while(i<A.length && j<B.length){    //两个数组都有元素时,开始循环
        if(A.data[i] < B.data[j]){      //判断谁小谁就先赋值到C表中
            C->data[k] = A.data[i];
            k++;
            i++;
        }else{
            C->data[k] = B.data[j];
            k++;
            j++;
        }
    }
    //当有一个数组元素用完时,就不满足循环条件了,此时另一个数组还有元素时,需要重新写循环
    while(i<A.length){     //当A表还有元素时,赋到C表中
        C->data[k] = A.data[i];
        k++;
        i++;
    }
    while(j<B.length){      //当B表还有元素时,赋到给C表中
        C->data[k] = B.data[j];
        k++;
        j++;
    }
    C->length = k;  //长度就是k,k多操作了一下没用,下标k变成了长度k
    return 1;
}

【单链表】

1、单链表的结构体定义

/*
 * 单链表的结构定义
 */
typedef struct LNode{   //定义单链表结点类型
    int data;           //数据域
    struct LNode *next; //指针域
}LNode, *LinkList;  //LNode别名,指向LNode 的指针类型

void main() {
    LinkList L;    //LinkList更强调的是一个链表
    LNode *p;      //LNode更强调的是一个结点
    p = L->next;
}

2、单链表的遍历输出

/*
 * 单链表的遍历操作
 */
void ListVisit(LinkList L){
    LNode *p = L->next;     //首先把头指针指向下一个结点的这个指针赋给指针p
    //LNode *p = L;         //【不带头节点】的,此时L就是第一个结点
    while(p != NULL){       //p指针会一直往下遍历,直到指向null时候停止
        printf("%d",p->data);
        p = p->next;        //赋值前,指针是指向结点的,赋值后,指针是指向下一个结点
    }                       //可以理解成每个结点都有指向下个结点的指针,就是把指向下一个结点的指针赋给p指针
}

3、单链表的按位查找和按值查找

/*
 * 单链表的按位查找
 * 有一个带头结点的单链表L,请设计一个算法查找第i个结点位置,若存在则返回该结点的指针
 */
LNode *Search_i(LinkList L, int i){
    if(i < 1){
        return NULL;
    }
    int k = 1;
    LNode *p = L->next;
    while(p!=NULL && k<i){  //遍历操作
        p = p->next;
        k++;
    }
    return p;   //当以p!=NULL跳出循环时,说明没找到,当以k=i跳出循环时,说明找到了
}
/*
 * 单链表的按值查找
 * 有一个带头结点的单链表L,请设计一个算法查找其第1个结点数据值为e的元素,若存在返回该结点的指针
 */
LNode *Search_e(LinkList L, int e){
    LNode *p = L->next;
    while(p!=NULL && p->data!=e){
        p = p->next;
    }
    return p;
}

4、头插法建立单链表

/*
 * 头插法建立单链表
 * 才用头插法在头指针为L处建立一个带头结点的单链表,输入-1表示结束,结果返回建立的单链表
 */
LinkList *List_HeadInsert(LinkList L){     //*加不加都无所谓,都会改变单链表L的
    L = (LNode *)malloc(sizeof(LNode));
    L->next = NULL;    //初始化头结点,保证最后指针为NULL
    LNode *S;   //要插入的结点
    int x;      //插入的结点的data
    scanf("%d", &x);    //手动输入x
    while(x != -1){     //输入-1时退出
        S = (LNode *) malloc(sizeof(LNode));    //插入一个就分配一个空间
        S->data = x;    //x赋给S结点的data
        S->next = L->next;  //L指针指向的下一个赋给新结点S,第一插入时是NULL
        L->next = S;    //L指针指向S
        scanf("%d", x);     //再次接收下一个
    }
    return L;
}

5、尾插法建立单链表

/*
 * 尾插法建立单链表
 * 采用尾插法在头指针为L处建立一个带头结点的单链表,输入-1表示结束,结果返回建立的单链表
 */
LinkList *List_TarlInsert(LinkList L){
    L = (LNode *) malloc(sizeof (LNode));   //分配头指针空间
    L->next = NULL; //初始化,在尾插法中可有可无,因为最后会让尾指针=NULL
    LNode *S, *r=L; //定义两个指针,一个是新结点S,一个是指向链表最后元素的指针
    int x;  //输入的元素data
    scanf("%d", x);
    while(x != -1){
        S = (LNode *) malloc(sizeof (LNode));   //为新结点分配空间
        S->data = x;    //新结点的data
        r->next = S;    //最后个元素指针指向S,意思是插入了新节点S
        r = S;      //更新r,使r还是指向最后一个元素,跟初始话LNode *r = L;意思一样的,只不过插入了新元素
        scanf("%d", x);
    }
    r->next = NULL; //最后r指向的是最后一个元素,r.next是指向下一个,下一个为NULL,故没有元素,所以上面的L.next=NULL没有关系
    return L;
}

【注】头插法注意防断链

           尾插法注意最后指针—>NULL

6*、单链表的就地逆置

/*
 * 单链表的就地逆置
 * 编写算法将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为O(1)
 */
void *Reverse(LinkList L){
    LNode *p = L->next, *r; //把头结点指向下一个结点A的指针赋给p,(暂时称头结点后第一个结点为A)
    L->next = NULL;     //头结点和下一个结点A断开
    while (p != NULL){
        r = p->next;    //指向A结点指向下一个结点的指针赋给r,防止丢失链
        p->next = L->next;  //L.next是NULL,赋给p.next,也就是结点A下一个指针是NULL没有元素了
        L->next = p;    //L指针指向p,就是指向A
        p = r;  //更新r,再来处理下一个结点B,头插法插入就是逆置了
    }
}

 

 

7、将一个单链表分解为两个单链表且相对顺序不变

/*
 * 将一个单链表分解成两个两个单链表且相对顺序不变
 * 将一个带头结点的单链表A分解成两个带头结点的单链表A和B,使得A表中含有原表中序号为奇数的元素,
 * 而B表中含有原表中序号为偶数的元素,且保持顺序不变,最后返回B表
 */
LinkList Create_A_B(LinkList A){
    LinkList B = (LinkList) malloc(sizeof (LNode));
    B->next = NULL; //初始化
    LNode *p = A->next, *ra, *rb;   //遍历指针p,尾插法,需要尾指针
    A->next = NULL; //A表断链操作
    while (p != NULL){  //两个结点为一组,第一个给A表,第二个给B表
        ra->next = p;   //A的头指针指向p
        ra = p; //更新尾指针的位置移到p结点
        p = p->next;    //p继续往后遍历
        if(p != NULL){  //第二个元素有没有
            rb->next = p;   //B的头指针指向p
            rb = p;     //更新尾指针的位置移到p结点
            p = p->next;    //p继续往后遍历
        }
    }
    ra->next = NULL;    //尾指针要置空
    rb->next = NULL;
    return B;
}

8、将一个单链表分解为两个单链表且相对顺序改变

/*
 * 将一个单链表分解成两个两个单链表且相对顺序不变
 * 编写一个算法将一个带头结点的单链表A={a1,b1,a2,b2,...,an,bn}
 * 分解成两个带头结点的单链表A和B,使得A={a1,a2...an},B={bn,...,b2,b1}
 */
LinkList Create_A_B2(LinkList A){
    LinkList B = (LinkList) malloc(sizeof (LNode));
    B->next = NULL;
    LNode *p = A->next, *r = A, *q; //遍历指针p,尾指针r,防断链指针q
    A->next = NULL; //A链表断开
    while(p != NULL){   //尾插法
        r->next = p;
        r = p;
        p = p->next;
        if(p != NULL){  //头插法
            q = p->next;    //防断链
            p->next = B->next;
            B->next = p;
            p = q;
        }
    }
    r->next = NULL; //头插法注意防断链,尾插法注意最后指针->NULL
    return B;
}

9、两个有序单链表合并为一个有序单链表

/*
 * 【两个单链表合并成一个有序单链表】
 * 假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。
 * 请编写算法将这两个带头结点的单链表归并为一个按元素值递减次序排列的单链表,
 * 并要求利用原来两个单链表的结点存放归并后的单链表。
 */
LinkList MergeList(LinkList A, LinkList B){
    LNode *p = A->next, *q = B->next;
    LNode *r;   //防断链
    A->next = NULL;
    while (p != NULL && q != NULL){
        if(p->data <= q->data){
            r = p->next;
            p->next = A->next;
            A->next = p;
            p = r;
        }
        else{
            r = q->next;
            q->next = A->next;
            A->next = q;
            q = r;
        }
    }
    if(q != NULL){  //B表剩余了
        p = q;
        while (p != NULL){
            r = p->next;
            p->next = A->next;
            A->next = p;
            p = r;
        }
        free(B);
        return A;
    }
}

10*、删除单链表中最小值结点(带头结点与不带头节点)

/*
 *删除单链表中最小值结点(带头结点与不带头结点)
 *试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点唯一)
 * */

//带头结点
void Del_Min(LinkList L){   //要把结果带回来,考试手写(LinkList &L)
    LNode *pre=L, *p=L->next;   //本题目中不仅要找到最小的结点,还要找到最小的前一个结点
    LNode *minpre=pre, *minp=p; //记录p遍历过程中,遇到的最小的
    while(p != NULL){
        if(p->data < minp->data){   //如果p指针比minp的还要小,更新minp
            minpre = pre;
            minp = p;
        }
        pre = p;    //p指针继续遍历
        p = p->next;
    }
    minpre->next = minp->next;  //删除最小值操作
    free(minp);
}

//不带头结点
void Del_Min2(LinkList L){  //要把结果带回来,考试手写(LinkList &L)
    LNode *pre, *p=L;
    LNode *minpre, *minp=p;
    while(p != NULL){
        if(p->next < minp->data){
            minp = p;
            minpre = pre;
        }
        pre = p;
        p = p->next;
    }
    if(minp == L){  //这里跟带头结点的不一样,如果最小的就是第一个
        L = L->next;
        free(minp);
    }
    else{
        minpre = minp->next;    //不是第一个的话跟带头结点的一样删除
        free(minp);
    }
}

 

 

【注】还有一种通用的办法 ,就是手动给他创建一个头结点,最后再free掉

//方法三,手动创建一个头结点,最后再free掉
void Del_Min3(LinkList L){
    
    //【创建头结点】
    LNode *head = (LNode *) malloc(sizeof(LNode));  //创建一个头结点
    head->next = L; //指向原来的链表

    //接下来的操作跟带头结点的一样
    LNode *pre=L, *p=L->next;   //本题目中不仅要找到最小的结点,还要找到最小的前一个结点
    LNode *minpre=pre, *minp=p; //记录p遍历过程中,遇到的最小的
    while(p != NULL){
        if(p->data < minp->data){   //如果p指针比minp的还要小,更新minp
            minpre = pre;
            minp = p;
        }
        pre = p;    //p指针继续遍历
        p = p->next;
    }
    minpre->next = minp->next;  //删除最小值操作
    free(minp);

    //【free掉头结点】
    L = head->next;
    free(head); //把创建的头结点free掉
}

11、删除单链表中所有值为x的结点

/*
 * 删除单链表中所有值为x的结点
 * 在带头结点的单链表L中,删除所有值为x的结点,并释放其空间
 * 假设值为x的结点不唯一,试编写算法以实现上述操作。
 * */
void Del_x(LinkList L, int x){
    LNode *pre=L, *p=L->next;   //pre指头结点,p指头结点的下一个,就是第一个元素
    while(p != NULL){
        if(p->data == x){   //如果p的data与x相等
            pre->next = p->next;    //则让pre直接指向p的下一个
            free(p);    //删掉p
            p = pre->next;  //p再回来,依然是pre的前面那个
        }
        else{
            pre = p;    //没有符合x条件的话,就正常遍历下一个
            p = p->next;
        }
    }
}

【注】单链表的删除操作中,最重要的是找到要删除的结点的前驱,所以要定义两个指针。 

12、删除单链表中介于给定两个值之间的结点

/*
 * 删除单链表中介于给定两个值之间的结点
 * 设在一个带头结点的单链表L中所有元素结点的数据值无序,试编写一个函数,
 * 删除单链表中所有数据域中值介于给定的两个值之间的结点。
 * (假设给定两个值分别是min和max)
 * */
void Del_min_max(LinkList L, int min, int max){
    LNode *pre=L, *p=L->next;
    while(p != NULL){
        if(p->data > min && p->data < max){ //给定的条件中间
            pre->next = p->next;    //这个结点符合条件,那就删掉
            free(p);    //释放
            p = pre->next;  //p指针回来
        }else{
            pre = p;    //不满足就继续往前遍历
            p = p->next;
        }
    }
}

13、删除一个有序单链表中重复的结点

/*
 * 删除一个有序单链表中值重复的结点
 * 设有一个单链表L,L中结点非递减有序排列
 * 设计一个算法删除单链表L中数值相同的结点,使单链表中不再有重复的结点,
 * 例如(7,10,10,21,30,42,42,42,51,70)变为(7,10,21,30,42,51,70)
 * */
void Del_Same(LinkList L){        //递增序列(1,2,4,6),非递减序列(1,2,2,4,5,6,6)
    if(L->next == NULL){    //先判空,空序列的话直接退出了
        return;
    }
    LNode *pre=L, *p=L->next;
    while(p != NULL){
        if(p->data == pre->data){   //如果p指针跟它的前驱结点data相同,则删掉p
            pre->next = p->next;    //删掉p操作
            free(p);    //释放
            p = pre->next;  //p指针再回来
        }else{
            pre = p;    //不相等,继续往回遍历
            p = p->next;
        }
    }
}

14、两个单链表中值相同的结点生成单链表C

/*
 * 两个单链表中值相同的结点生成单链表C
 * 设A和B是两个带头结点的单链表,其中结点值递增有序。
 * 请设计一个算法从单链表A和B中找出值相同的结点,
 * 并生成一个新的单链表C,要求不破坏A、B的结点
 * */
LinkList Creat_Common(LinkList A, LinkList B){  //不用写回地址
    LNode *p=A->next, *q=B->next;   //p指针指向A表,q指针指向B表
    LNode *r, *s;   //辅助新结点的创建
    LinkList C = (LinkList) malloc(sizeof(LNode));  //创建C的头结点
    C->next = NULL; //习惯性,想让C置空
    r = C;  //让r指向C表
    while(p != NULL && q != NULL){
        if(p->data < q->data){  //第一种情况A表元素小于B表元素,让A表遍历,直到等于或者大于B表元素
            p = p->next;
        }
        else if(p->data > q->data){ //第二种情况A表元素大于B表元素,让B表遍历,直到等于或者大于A表元素
            q = q->next;
        }
        else {  //第三种情况A表元素等于B表元素
            s = (LNode *)malloc(sizeof(LNode)); //给s赋予空间,准备接收新元素,用尾插法插入C
            s->data = p->data;  //插入p或q都一样,data相同,把data赋予s结点
            r->next = s;    //r的指向s,就是头结点C的下一个元素是新的s
            r = s;  //遍历r,让它指向s,再来新元素的时候就是s后面接着新元素了
            p = p->next;    //两个表继续遍历
            q = q->next;
        }
        r->next = NULL; //全部插完之后,r是指向最后一个元素的,让r的下一个置空
                        //【尾插法注意尾指针->NULL,头插法注意防锻炼】
        return C;
    }
}

15、求A和B两个链表的交集

/*
 * 求A和B两个链表的交集
 * 设A和B是两个带头结点的单链表,其中结点值递增有序。
 * 请设计一个算法,求单链表A和B的交集,并存放于A链表中,其它结点均释放其空间。
 * */
LinkList Union(LinkList A, LinkList B){ //要写回地址&
    LNode *p=A->next, *q=B->next;   //p指针指向A表,q指针指向B表
    LNode *r=A, *u; //尾指针,辅助指针
    while(p != NULL && q !=NULL){
        if(p->data < q->data){  //第一种情况A表元素小于B表元素,让A表遍历,同时free掉,直到等于或者大于B表元素
            u = p;  //把p所指的给u
            p = p->next;    //p先走下一个
            free(u);    //u开始动手 free掉
        }
        else if(p->data > q->data){ //第二种情况A表元素大于B表元素,让B表遍历,同时free掉,直到等于或者大于A表元素
            u = p;  //把p所指的给u
            q = q->next;    //q先走
            free(u);    //u动手
        }
        else{   //第三种情况,A,B表相同,就是要找的交集
            r->next = p;    //r本来指向A表头的,就是A头结点,头结点下一个是交集结点
            r = p;  //r遍历,由表头,再到下一个结点,再到下一个结点,(尾插法)
            p = p->next;    //A表,p也遍历
            u = q;  //B表也要遍历,遍历的过程中,把交集给u去free掉
            q = q->next;    //q走
            free(u);    //u开始动手
        }
    }
    while(p != NULL){   //当B表遍历完了时,A边继续遍历free掉
        u = p;
        p = p->next;
        free(u);
    }
    while(q != NULL){   //当A表遍历完了时,B边继续遍历free掉
        u = q;
        q = q->next;
        free(u);
    }
    r->next = NULL; //尾插法注意尾指针置空
    free(B);    //B的表头也free
    return A;
}

16、递增次序输出单链表中各结点数据值

/*
 * 递增次序输出单链表中各结点数据值
 * 有一个带头结点的单链表,其头指针为head,试编写一个算法,
 * 按递增次序输出单链表中各结点的数据值,
 * 并释放所有结点所占的存储空间(要求不允许使用数组作为辅助空间)
 * */
void Print_Min(LinkList head){  //写回地址&

    while(head->next != NULL){  //如果链表不是空的,那就要开始操作了,每一趟可以找出一个最小的,给它free掉
        LNode *pre=head, *p=head->next; //找最小值的遍历指针,和他的前驱指针
        LNode *minpre=head, *minp=head->next;   //记录最小p的指针,和他的前驱指针
        while(p != NULL){   //找最小值操作
            if(p->data < minp->data){
                minp = p;
                minpre = pre;
            }
            pre = p;
            p = p->next;
        }   //当这个while循环结束了之后就意味着找到了一个最小值,接下来就给他打印出来
        printf("%d", minp->data);
        minpre->next = p->next; //删掉最小结点
        free(minp);
    }

    free(head); //最后free掉头结点
}

17*、判断B链表是否为A链表的连续子序列(朴素的模式匹配)

/*
 *判断B链表是否为A链表的连续子序列
 * 两个整数序列A={a1,a2,a3,...,am}和B={b1,b2,b3,...,bn}已经存入两个单链表中
 * 设计一个算法,判断序列B是否是A的连续子序列。
 * (模式串匹配问题)
 * */
//朴素的模式匹配
int pattern(LinkList A, LinkList B){
    LNode *p=A->next, *q=B->next;   //p指着A主串,q指着B模式串
    LNode *pre=A->next;   //对比失败后,主串的p需要回退,记录回退到什么位置
    while(p != NULL && q != NULL){  //主串,模式串都不能为空,模式串为空,说明匹配成功
        if(p->data == q->data){ //如果比较成功
            p = p->next;    //继续比下去
            q = q->next;
        }
        else{   //比较失败
            pre = pre->next;    //pre原来是p开始位置,现在到下一个位置,
            p = pre;    //p回来
            q = B->next;    //q还是头结点下一个
        }
    }
    if(q == NULL){  //判断跳出循环的原因,
        return 1;   //q如果遍历到为空了,说明是匹配成功的
    }else{  //如果是主串p为空,说明是匹配失败的
        return 0;
    }
}

18、判断链表是否有环

/*
 *判断单链表是否有环
 * 请设计一个算法判断单链表L是否有环
 * */
//算法的思路是:1、定义一个快指针(每次走两步),一个慢指针(每次走一步);2、如果他们相遇了一定是有环的
int FindLoop(LinkList L){   //不需要引用符
    LNode *fast=L, *slow=L;
    while(fast != NULL && fast->next != NULL){  //快指针肯定先到终点,
                                                //结束的条件由于结点的奇数个偶数个影响,两个条件
                                                //快指针可能一下子跑到NULL外面去了,也有可能是刚好到最后一个结点,所以用&&
        slow = slow->next;  //慢指针每次走一步
        fast = fast->next->next;    //快指针每次走两步
        if(slow == fast){   //如果快指针慢指针相遇了,那就一定有环(数学推理)
            return 1;   //有环
        }else{
            return 0;   //无环
        }

    }
}

19、链接两个循环单链表

/*
 *链接两个循环单链表
 * 有两个不带头结点的【循环单链表】,链表头指针分别为h1和h2,
 * 编写一个函数将链表h2连接到链表h1后,要求链接后的链表仍然保持循环单链表形式
 * */
LinkList Linkh1_h2(LinkList h1, LinkList h2){   //带有引用&
    LNode *p, *q;
    p = h1;
    while(p->next != h1){   //我们要找到指针h1的结点,也就是尾结点(循环链表)
        p = p->next;
    }
    q = h2;
    while(q->next != h2){   //我们要找到指针h2的结点,也就是尾结点(循环链表)
        q = q->next;
    }
    p->next = h2;   //链接h1,h2
    q->next = h1;   //最后的指向整体链表的头指针
    return h1;
}

20、递增次序输出循环单链表中各结点的数据值

/*
 *递增次序输出循环单链表中各结点数据值
 * 设有一个带头结点的【循环单链表】L,其结点值均为正整数。
 * 设计一个算法,反复找出单链表中结点值最小的结点并输出,
 * 然后将该结点从中删除,知道单链表空为止,在删除头结点。
 * */
void Print_Min2(LinkList L){    //需要引用&

    while(L->next != L){    //循环单链表,尾指针不指NULL,而是指着头,每一趟循环可以找出一个最小值,给它free掉
        LNode *pre=L, *p=L->next;   //遍历过去
        LNode *minpre=pre, *minp=p; //记录遍历过程中最小的
        while(p != L) {
            if (p->data < minp->data) { //如果找到了更小的
                minp = p;   //更新给minp
                minpre = pre;
            }
            pre = p;    //p指针继续遍历
            p = p->next;
        }
        printf("%d",minp->data);    //打印
        minpre->next = minp->next;  //删除操作
        free(minp);     //释放空间
    }

    free(L);    //最后再释放头指针
}

21、判断循环双链表是否对称

/*
 *判断【循环双链表】是否对称
 *设计一个算法用于判断带头结点的循环双链表L是否对称
 * */
//循环双链表的结构定义
typedef struct DNode{
    int data;
    struct DNode *prior, *next;
}DNode, *DLinkList;

//判断是否对称
int Func(DLinkList L){  //不用&
    DNode *p=L->next, *q=L->prior;  //定义两指针,分两头跑
    while(p != q && q->next != p){  //结束的条件,由除去头结点,剩余的结点奇数个,偶数个影响,有两种结果
        if(p->data == q->data){     //一个往前走走,一个往后走,判断是否相等
            p = p->next;
            q = q->prior;
        }
        else{
            return 0;   //不对称
        }
        return 1;   //对称
    }
}

【栈和队列】

1、栈的结构体定义和基本操作

/*
 * 栈的结构体定义及基本操作
 * */
//链栈很少使用。。。
#define MaxSize 50
//顺序栈结构体定义
typedef struct {
    int data[MaxSize];
    int top;
}Stack;

//初始化
void InitStack(Stack s){    //要写回地址 void InitStack(Stack &S)
    s.top = -1; //栈顶指针为-1的时候,栈里面就是空的,有元素进来就+1
                //结合图片理解
}

//判断栈是否为空
int IsEmpty(Stack s){
    if(s.top == -1){
        return 1;   //栈顶指针为-1就是空的
    } else{
        return 0;   //不是空的
    }
}

//压栈操作
int Push(Stack s, int x){   //要写回地址 int Push(Stack &S, int x)
    if(s.top == MaxSize-1){
        return 0;   //栈满了,压栈失败
    }else{
        s.top++;
        s.data[s.top] = x;
        //上面两句可以合起来
        //s.data[++s.top] = x;
        return 1;   //压栈成功
    }
}

//出栈操作
int Pop(Stack s, int x){    //要写回地址 int Push(Stack &S, int &x)  出栈的元素赋值给x
    if(s.top == -1){
        return 0;   //空栈
    } else{
        x = s.data[s.top];  //先x记录一下出栈的元素
        s.top--;
        //上面两句可以合起来
        //x = s.data[s.top--];
        return 1;
    }
}

//查看栈顶元素是谁
int GetTop(Stack s, int x){     //x要写回地址,记录一个是谁
    if(s.top == -1){
        return 0;
    } else{
        x = s.data[s.top];
        return 1;
    }
}

【注】i++和++i是C语言运算符,i++是后缀递增的意思,++i是前缀递增的意思。

x = i ++;   //先让x变成i的值1,再让i加1

x = ++i;    //先让i加1, 再让x变成i的值1

2、判断n个字符是否为中心对称

/*
 * 判断n个字符是否为中心对称
 *有一个带头结点的单链表L,结点结构由data和next两个域构成,
 * 其中data域为字符型。请设计一个算法判断该链表的全部n个字符是否中心对称。
 * 例如xyx,xyyx都是中心对称。
 * */
//思路:先取链表的前半部分,前半部分先入栈,然后再出栈,再与后半部分一一对比
//问题:遍历到中间时,字符的奇数个需要跳过去,偶数个跟着入栈
int Func(LinkList L, int n){
    char s[n/2];  //虽然定义的是一个数组,但我按照栈的操作来处理
    int i;  //栈顶指针

    LNode *p = L->next; //遍历指针,指向第一个数据结点
    for(i=0; i<n/2; i++){   //由于是一个int型,所以3/2=1,因此奇数个结点的中间结点不会入栈
        s[i] = p->data; //入栈
        p = p->next;    //指针遍历
    }   //前半个结点入栈成功
    i--;    //让栈顶指针回来一位,刚好指着栈顶元素
    if(n%2 == 1){   //如果是奇数个怎么办
        p = p->next;    //那就跳过去
    }
    while(p != NULL){
        if(p->data == s[i]){
            i--;    //逻辑上出栈操作
            p = p->next;    //p继续遍历
        } else{
            return 0;   //不对称
        }
    }
    return 1;   //没有问题的执行完while循环,那就对称
}

3*、判断算术表达式是否匹配【栈的应用】

/*
 *判断算术表达式是否匹配【栈的应用】
 * 假设一个算术表达式中包含小括号和中括号2中类型的括号,编写一个算法来判别表达式中的括号是否配对,
 * 假设算术表达式存放于字符数组中,以字符’\0‘作为算术表达式的结束符。
 * */
//思路:①遍历到左括号,入栈 ②遍历到右括号,出栈对比两者
int BracketsCheck(char a[]){
    Stack s;    //定义了一个栈
    InitStack(s);   //初始化栈
    char x; //当遍历到右括号的,就需要从栈里拿一个出来赋给x进行对比
    for(int i=0; a[i] != '\0'; i++){
        switch(a[i]){   //写for循环就太麻烦了,用switch写出全部情况
            case '(' :
                Push(s, a[i]);
                break;
            case '[' :
                Push(s, a[i]);
                break;
            case ')' :
                if(IsEmpty(s)){ //判断栈是空的话,而先遍历到了右括号,则匹配失败
                    return 0;
                } else{
                    Pop(s, x);
                    if(x != '(' ){   //如果不是这个左括号,匹配失败
                        return 0;
                    }
                    break;
                }
            case ']' :
                if(IsEmpty(s)){ //判断栈是空的话,而先遍历到了右括号,则匹配失败
                    return 0;
                } else{
                    Pop(s, x);
                    if(x != '[' ){   //如果不是这个左括号,匹配失败
                        return 0;
                    }
                    break;
                }
            default :   //其它的情况,比如遍历到数字,就不去操作
                break;
        }

    }
    if(IsEmpty(s)){ //遍历完了之后,右括号没了,但栈里面还有左括号,那说明还是匹配失败的
        return 1;
    } else{
        return 0;
    }
}

4、两个栈共享一个存储区

/*
 * 两个栈共享一个存储区
 * 有两个栈s1、s2都采用顺序栈的方式,并共享一个存储区[0,...,maxsize-1],
 * 为了尽量利用空间,减少溢出的可能,可采用栈顶相向、迎面增长的存储方式。
 * 试设计写出此栈的定义和s1、s2有关入栈和出栈的操作算法。
 * */
#define MaxSize 50
typedef struct {
    int data[MaxSize];
    int top[2]; //两个栈顶指针
}Stackk;

//初始化
void InitStackk(Stackk ss){    //要写回地址 void InitStack(Stack &S)
    ss.top[0] = -1;         //两个相向的栈顶指针
    ss.top[1] = MaxSize;
    //结合图片理解
}

//入栈操作
int Pushh(Stackk ss, int i, int x){    //i为了区分是哪个栈进行压栈操作 i=0的是s1栈进行入栈,i=1是s2栈进行入栈
                                      // 要加引用符 &ss,&x
    if(i<0 || i>1){
        return 0;   //压栈失败
    }
    if(ss.top[1] - ss.top[0] == 1){
        return 0;   //栈满了,不能再入栈
    } else{ //如果栈没有满
        switch (i) {
            case 0 :    //当i=0的时候是对下面那个栈操作
                ss.top[0]++;
                ss.data[ss.top[0]] = x;
                break;
            case 1 :    //当i=1的时候是对上面那个栈操作
                ss.top[1]--;
                ss.data[ss.top[1]] = x;
                break;
        }
        return 1;   //入栈成功
    }
}

//出栈操作
int Popp(Stackk ss, int i, int x){  //i为了区分是哪个栈进行压栈操作 i=0的是s1栈进行入栈,i=1是s2栈进行入栈
                                    //要加引用符 &ss
    if(i<0 || i>1){
        return 0;   //不符合条件
    }
    switch (i) {
        case 0 :    //当i=0的时候是对下面那个栈操作
            if(ss.top[0] == -1){    //栈空了何谈去出栈
                return 0;
            }else{
                x = ss.data[ss.top[0]--];   //用x来接收一下出栈的元素
            }
            break;
        case 1 :    //当i=1的时候是对上面那个栈操作
            if(ss.top[0] == -MaxSize){    //栈空了何谈去出栈
                return 0;
            }else{
                x = ss.data[ss.top[1]++];   //用x来接收一下出栈的元素
            }
            break;
    }
    return 1;   //出栈成功
}

5*、队列的结构体定义及基本操作

/*
 * 队列的结构体定义及基本操作
 * */
#define MaxSize 50
//结构体定义
typedef struct{
    int data[MaxSize];
    int front, rear;    //两个指针,front队头指针,rear队尾指针
}Queue;

//初始化
void InitQueue(Queue q){    //要&q
    q.front = 0;
    q.rear = 0;
}

//判空
int IsEmpty(Queue q){
    if(q.front == q.rear){
        return 1;   //如果两个指针相同,则队列为空
    }else{
        return 0;
    }
}

//入队操作
int EnQueue(Queue q, int x){   //要&q
    if(q.rear == MaxSize){  //【判满】的操作就是看队尾指针到头没
        return 0;   //队列满了
    }else { //入队操作
    q.data[q.rear] = x; //队尾指针用来入队
    q.rear++;
    return 1;
    }
}

//出队操作
int DeQueue(Queue q, int x){  //要&q,&x
    if(q.front == q.rear){
        return 0;   //空队就不用谈出队了
    }else{
        x = q.data[q.front];    //用x来接收一下出队元素
        q.front++;  //队头指针用来出队
        return 1;
    }
}

//循环队列
int IsEmptyCir(Queue q){
    if((q.rear+1)%MaxSize == q.front){
        return 0;   //让rear移动一个位置,如果等于front,那就是满了,移动指针是新的取余写法
    }else{
        return 1;
    }
}

//入队
int EnCirQueue(Queue q, int x){    //需要 &q
    if((q.rear+1)%MaxSize == q.front) {
        return 0;   //满了还怎么入队
    }else { //入队操作
        q.data[q.rear] = x; //队尾指针用来入队
        q.rear = (q.rear+1)%MaxSize;    //不再是单纯的rear++了,而是新的取余写法
        return 1;
    }
}

//出队
int DeCieQueue(Queue q, int x){  //要&q,&x
    if(q.front == q.rear){
        return 0;   //空队就不用谈出队了
    }else{
        x = q.data[q.front];    //用x来接收一下出队元素
        q.front = (q.front+1)%MaxSize;  //不再是单纯的front++了,而是新的取余写法
        return 1;
    }
}

6*、使用标志域的循环队列入队出队算法

/*
 * 使用标志域的循环队列入队出队算法
 * 若希望循环队列中的元素都能得到利用,则需要设置一个标志与tag,
 * 并以tag的值为0或1来区分队头指针front和队尾指针rear相同时的队列状态是“空”还是“满”。
 * 试编写与此结构相应的入队和出队算法。
 * */
//结构体定义
typedef struct{
    int data[MaxSize];
    int front, rear;    //两个指针,front队头指针,rear队尾指针
    int tag; //当rear=front时,tag为1说明队列是满的,tag为0说明队列是空的
}QueueTag;

//入队
int EnQueueTag(QueueTag q, int x){    //需要 &q
    if(q.front == q.rear && q.tag == 1){
        return 0;   //当rear=front时,tag为1说明队列是满的
    }else{
        q.data[q.rear] = x;
        q.rear = (q.rear+1)%MaxSize;
        q.tag=1;    //一棍子打死写法,只要入队了 tag就为1,当下次rear==front时就是满的
    }
}

//出队
int DeQueaeTag(QueueTag q, int x){     //需要 &q, &x
    if(q.front == q.rear && q.tag == 0){
        return 0;   //当rear=front时,tag为0说明队列是空的
    }else{
        x = q.data[q.front];
        q.front = (q.front+1)%MaxSize;
        q.tag=0;    //一棍子打死写法,只要出队了 tag就为1,当下次rear==front时就是空的
    }
}

7、借助栈实现队列逆置

/*
 * 借助栈实现队列逆置
 * q是一个队列,s是一个空栈,实现将队列中的元素逆置的算法
 * */
void Reverse(Queue q, Stack s){ //需要 &q
    int x;
    while(!IsEmpty(q)){ //队列不为空
        DeQueue(q, x);  //将元素出队赋值给x
        Push(s, x); //将元素x放入栈
    }
    while(!IsEmptyStack(s)){ //栈不为空
        Pop(s, x);  //将栈的元素出栈赋值给x
        EnQueue(q, x);  //将元素x放入队列
    }
}

【树】

1、递归函数的核心思想

/*
 * 递归的核心思想
 * 利用递归求解n的阶乘
 * */
int Func(int n){
    if(n == 0){     //递归边界*
        return 1;
    }else{      //递归式*
        return n* Func(n-1);
    }
}
/*
 * 斐波那契数列是满足F(0)=1,F(1)=1,F(n)=F(n-1)+F(n-2) (n≥2) 的数列
 * 数列的前几项为1,1,2,3,5,8,13,21....写出求解斐波那契数列第n项的程序
 * */
int Funcc(int n){
    if(n==0 || n==1){
        return 1;
    }else{
        return Funcc(n-1) + Funcc(n-2);
    }
}

2、二叉树的链式存储结构体定义

/*
 * 二叉树的链式存储结构体定义
 * */
typedef struct BiTNode{
    int data;
    struct BiTNode *lchild, *rchild;    //左孩子指针,右孩子指针
}BiTNode, *BiTree;  

  

3、二叉树的三种递归遍历算法

/*
 * 二叉树的三种递归遍历算法
 * 请分别写出二叉树的先序、中序、后序递归遍历算法。
 * */
//思路:按照先序遍历算法,先遍历根结点,其次是他的左子树,再右子树
//问题:右子树不是单单一个结点,因此对右子树也进行先序遍历操作,递归着下去...

//先序遍历
void PreOrder(BiTree T){
    if(T != NULL) { //递归边界,是子树为空的时候
        //递归式
        printf("%d", T->data);  //先打印根
        PreOrder(T->lchild);    //递归遍历左子树
        PreOrder(T->rchild);    //递归遍历右子树
    }
}
//中序遍历
void InOrder(BiTree T){
    if(T != NULL) { //递归边界,是子树为空的时候
        //递归式
        InOrder(T->lchild);    //递归遍历左子树
        printf("%d", T->data);  //打印根
        InOrder(T->rchild);    //递归遍历右子树
    }
}
//后序遍历
void PostOrder(BiTree T){
    if(T != NULL) { //递归边界,是子树为空的时候
        //递归式
        PostOrder(T->lchild);    //递归遍历左子树
        PostOrder(T->rchild);    //递归遍历右子树
        printf("%d", T->data);  //打印根
    }
}

4、查找二叉树中数据域等于key的结点

/*
 * 查找二叉树中数据域等于key的结点
 * 在一棵以二叉链表为存储的二叉树中,查找data域值等于key的结点是否存在,
 * 如果存在,则将指针q指向该结点,假设data为int型。(二叉树中结点值都不相同)
 * */
//思路:把二叉树遍历一遍,看看有没有相等的值
void Search(BiTree T, BiTNode *q, int key){     //BiTNode &*q  有可能会改变q的值需要&
    if(T != NULL){  //递归边界
        //递归式
        if(T->data == key){     //先看看根
            q = T;
        }else{
            Search(T->lchild, q, key);  //递归寻找左子树
            Search(T->rchild, q, key);  //递归寻找右子树
        }
    }
}

5、打印先序遍历序列中第k个结点的数据值

/*
 * 打印先序遍历序列中第k个结点的数据值
 * 假设二叉树采用二叉链表形式存储,
 * 设计一个算法,求先序遍历序列中第k(1 ≤ k ≤ 二叉树中结点个数)个结点的值。
 * */
//思路:定义一个遍历n,每当遍历一个,就让n++
int n = 0;  //这个n只能定义全局遍历,递归函数会不断调用自己,因此局部遍历会一直int n=0;就无法完成累加了,每个函数都有自己的存储空间
void Search_k(BiTree T, int k){
    if(T != NULL){
        n++;    //计数
        if(k == n){ //遍历了k次时
            printf("%d", T->data);
            return;
        } else{
            Search_k(T->lchild, k); //递归找左子树
            Search_k(T->rchild, k); //递归找右子树
        }
    }
}

6*、利用递归计算二叉树中所有结点的个数

/*
 * 利用递归计算二叉树中所有结点的个数
 * */
//方法一:计数法,根据前序中序后序遍历算法思想改
int n1 = 0;
void Count1(BiTree T){
    if(T != NULL){
        n1++;
        Count1(T->lchild);
        Count1(T->rchild);
    }
}

 

//方法二:【通用的写法】适用于:二叉树中的所有结点、叶子结点、高度计算...
int Count(BiTree T){
    int n1,n2;  //只是记录此次递归的 左子树的个数n1,右子树的个数n2,所以可以定义在局部变量
    if(T = NULL){   //递归边界,为空不用遍历了
        return 0;
    }else{
        n1 = Count(T->lchild);  //n1接收一下左子树的个数
        n2 = Count(T->rchild);  //n2接收一下右子树的个数
        return n1+n2+1; //左子树的个数+右子树的个数+根,【注】递归中每次n1,n2都是对应不同根结点的n1,n2
    }
}

7、利用递归计算二叉树中叶子结点的个数

/*
 * 利用递归计算二叉树中叶子结点的个数
 * */
//法一:计数法
int n2 = 0;
void Count2(BiTree T){
    if(T != NULL){  //递归边界
        if(T->lchild==NULL && T->rchild==NULL){ //判断是否是叶子结点
            n++;    //计数
            Count2(T->lchild);  //递归左子树
            Count2(T->rchild);  //递归右子树
        }
    }
}
//方法二:通用写法
int CountYeZi(BiTree T){
    int n1,n2;
    if(T == NULL){  //递归边界
        return 0;   //空树何谈找叶子结点
    }else if(T->lchild==NULL && T->rchild==NULL){   //根结点满足左右子树为空就是叶子结点
        return 1;   //就是1个
    }else{
        n1 = CountYeZi(T->lchild);    //继续找左子树的
        n2 = CountYeZi(T->rchild);    //继续找右子树的
        return n1+n2;   //返回左子树和右子树的叶子结点总个数
    }
}

8、利用递归计算二叉树中所有双分支结点的个数

/*
 * 利用递归计算二叉树中所有双分支结点的个数
 * */
//问题:处理度为2,度为1,叶子结点,NULL的递归式会不一样
int CountDu2(BiTree T){
    int n1, n2;
    if(T == NULL){
        return 0;
    }else if(T->lchild != NULL && T->rchild != NULL){   //如果左子树不为空,右子树不为空【递归式不同】
        n1 = CountDu2(T->lchild);   //n1 = 再递归找找左子树的度为2的结点
        n2 = CountDu2(T->rchild);   //n2 = 再递归找找右子树的度为2的结点
        return n1+n2+1; //左子树的 + 右子树的度为2的结点个数 + 根也是
    }else{  //不满足左子树不为空 和 右子树不为空的(比如度为1的,度为0的)【递归式不同】
        n1 = CountDu2(T->lchild);   //找找左子树的
        n2 = CountDu2(T->rchild);   //找找右子树的
        return n1+n2;   //返回左子树,右子树找到的,由于根节点不是,所以不+1
    }
}

9*、利用递归计算二叉树的深度(高度)

/*
 * 利用递归计算二叉树的深度(高度)
 * */
int Depth(BiTree T){
    int ldep, rdep;
    if(T == NULL){  //递归边界,树为空就不用找了
        return 0;
    }else{
        ldep = Depth(T->lchild);    //递归找左子树高度
        rdep = Depth(T->rchild);    //递归找右子树高度
        if(ldep > rdep){    //比较哪个子树高
            return ldep+1;  //左子树高 + 根结点1层
        }else{
            return rdep+1;  //右子树高 + 根结点1层
        }
    }
}

10、交换一棵二叉树中所有结点的左右子树

/*
 * 交换一棵二叉树中所有结点的左右子树
 * 设树B是一颗采用二叉链表形式存储的二叉树,编写一个把树B中所有结点的左、右子树进行交换的函数
 * */
void Swap(BiTree B){    //需要 BiTree &B
    if(B != NULL){
        BiTNode *temp = B->lchild;  //定义一个辅助变量,初始状态先指着左孩子
        B->lchild = B->rchild;  //右边的指针域复制到左边的指针域
        B->rchild = temp;   //原来的的左指针域复制给右指针域
        Swap(B->lchild);    //递归操作左子树
        Swap(B->rchild);    //递归操作右子树
    }
}

11、查找值为x结点的层次号

/*
 * 查找值为x结点的层次号
 * 假设二叉树采用二叉链表存储结构,设计一个算法,求二叉树T中值为x的结点的层次号
 * */
void Serach_x_level(BiTree T, int x, int level){      //(int level = 1;)
    if(T != NULL){  //递归边界
        if(T->data == x){   //要找到就是这个
            printf("x所在的层数为%d", level);
        } else{ //否则找找左右子树
            Serach_x_level(T->lchild, x, level+1);  //递归到了下一层所以要加层次了
            Serach_x_level(T->rchild, x, level+1);
        }
    }
}

12*、二叉树的层次遍历算法

/*
 * 二叉树的层次遍历算法
 * */
//思路:借助队列完成,将根结点放入队列中,①如果队列不为空,出队一个,打印,②如果根结点还有左右孩子,让他们入队
void LevelOder(BiTree T){
    Queue Q;    //定义一个队列(考研中注重的是思路,可以简单写伪代码)
    InitQueue(Q);   //初始化队列
    BiTNode *p = T; //定义一个遍历指针p
    EnQueue(Q, p);  //入队操作
    while (!IsEmptyQueue(Q)){    //如果队列不为空呢
        DeQueue(Q, p);  //出队
        printf("%d", p->data);
        if(p->lchild != NULL){  //如果他还有左孩子,让他入队
            EnQueue(Q, p->lchild);  //此时的p指针一次后 p = p->lchild
        }
        if(p->rchild != NULL){    //如果他还有右孩子,让他入队
            EnQueue(Q, p->rchild);
        }
    }
}

13、反转层次遍历

/*
 * 反转层次遍历
 * 试写出二叉树自下而上、从右到左的层次遍历算法
 * */
//思路:与上一题差不多,还需要引用一个栈,出栈反转遍历
void ReLevelOrder(BiTree T){
    Queue Q;
    Stack S;
    InitQueue(Q);
    InitStack(S);
    BiTNode *p = T;
    EnQueue(Q, p);
    while (!IsEmptyQueue(Q)){   //当队列不为空的时候
        DeQueue(Q, p);  //出队一个元素
        Push(S, p); //把他压入栈中
        if(T->lchild != NULL){  //出队的这个元素还有左孩子右孩子,先按照层次遍历压入队列,出队后再压入栈
            EnQueue(Q, p->lchild);  //左孩子压入队列
        }
        if(T->rchild != NULL){
            EnQueue(Q, p->rchild);  //右孩子压入队列
        }
    }   //上面操作完就是全部压入栈了
    while(!IsEmptyStack(S)){    //栈的现进后出实现了反转
        Pop(S, p);  //S出栈的元素赋给指针变量p
        printf("%d", p->data);  //打印下p.data
    }
}

14*、判断一棵二叉树是否为完全二叉树

/*
 * 判断一棵二叉树是否为完全二叉树
 * 二叉树按二叉链表形式存储,写一个判别给定二叉树是否是完全二叉数的算法
 * */
//思路:层次遍历找到第一个为空的位置的时候,接下来的都应该为空,否则就不是完全二叉树
int IsCamplete(BiTree T){
    if(T == NULL){
        return 1;   //空树就是完全二叉树
    }else{
        Queue Q;
        InitQueue(Q);
        BiTNode *p = T;
        EnQueue(Q, p);  //这一部分都是层次遍历的套路
        while(!IsEmptyQueue(Q)){
            DeQueue(Q, p);
            if(p != NULL){
                EnQueue(Q, p->lchild);  //不管是不是NULL都把它左右孩子弄进去
                EnQueue(Q, p->rchild);
            }else { //如果出现了队列第一个NULL
                while(!IsEmptyQueue(Q)){    //循环看看队列后面剩下的结点是否为空
                    DeQueue(Q, p);  //出队,赋值给p
                    if(p != NULL){
                        return 0;   //已经出现了个为空的,后面都是空的才对,不是空的话,就不是完全二叉树
                    }else{
                        return 1;
                    }
                }
            }
        }
    }
}

15、删除二叉树中以值为x结点为根的子树

/*
 * 删除二叉树中以值为x结点为根的子树
 * 已知二叉树采用二叉链表形式存储,请设计一个算法完成:
 * 对于树中每个元素值为x的结点,删去以他为根的子树,并释放相应的空间。
 * */
//删除的操作应该是,找到那个子树,让后从子树自下而上的删除,不然删了根结点就找不到它的左右孩子了
//删除操作
void DeleteTree(BiTree T){
    if(T != NULL){
        DeleteTree(T->lchild);
        DeleteTree(T->rchild);
        free(T);    //实现了递归的自下而上删除free子树
    }
}
void Search_x(BiTree T, int x){     //需要 BiTree &T
    if(T == NULL){    //空树
        return; //不需要任何操作
    }
    if(T->data == x){
        DeleteTree(T);  //调用上面函数删除
        return;
    }   //上面为树的根的一个特殊情况
    Queue Q;    //还是层次遍历的改写
    InitQueue(Q);
    BiTNode *p = T;
    EnQueue(Q, p);  //根先入队
    while (!IsEmptyQueue(Q)){   //队列不是空的
        DeQueue(Q, p);  //先出队一个
        if(p->lchild != NULL){  //先怀疑一下它的左孩子
            if(p->lchild->data == x);   //左孩子就是话
            DeleteTree(p->lchild);  //调用函数给它删了
            p->lchild = NULL;   //左孩子置空
        }else {             //如果不是
            EnQueue(Q, p->lchild);  //给左孩子正常层次遍历,先入队
        }
        if(p->rchild != NULL){  //怀疑一下右孩子
            if(p->rchild->data == x);   //右孩子就是话
            DeleteTree(p->rchild);  //调用函数给它删了
            p->rchild = NULL;   //别忘了置空
        }else {             //如果不是的话
            EnQueue(Q, p->rchild);  //给右孩子正常层次遍历,先入队
        }
    }
}

16*、非递归算法求二叉树的高度

/*
 * 非递归算法求二叉树的高度
 * 二叉树采用二叉链表存储结构,设计一个非递归算法求二叉树高度
 * */
int Depth2(BiTree T){
    if(T == NULL){
        return 0;
    }else{
        int h = 0;  //记录高度
        int last = 0;   //记录每一层最后一个结点的位置
        BiTNode *Q[MaxSize];    //定义一个数组,指针变量类型,顺序队列
        int front = -1, rear = -1;  //初始化为-1,与先前的初始化为0不同
        BiTNode *p = T; //层次变量指针p
        Q[++rear] = p;  //根先放入队列
        while(front < rear){    //如果队列不为空
            p = Q[++front]; //出队,p来接收一下
            if(p->lchild){  //如果有左孩子
                Q[++rear] = p->lchild;  //入队
            }
            if(p->rchild){  //如果有右孩子
                Q[++rear] = p->rchild;  //入队
            }

            if(front == last){  //代表着这一层处理完了
                h++;
                last = rear;    //更新last位置
            }
        }
        return h;
    }
}

17、求二叉树的宽度

/*
 * 求二叉树的宽度
 * 假设二叉树采用二叉链表存储结构,设计一个算法,求给定的二叉树宽度
 * (即具有结点数最多的那一层的结点个数)
 * */
//思路:依旧离不开层次遍历,①层次遍历二叉树,同时更新level数组 ②遍历level数组
int Width(BiTree T){
    if(T == NULL){
        return 0;
    }
    BiTNode *Q[MaxSize];    //数组,用作于对列
    int front=0, rear=0;
    int level[MaxSize];
    BiTNode *p= T;  //遍历指针p
    int k = 1;  //记录p指针指向结点的第几层,层数;p变化,让k也同步的变化
    Q[rear] = p;    //根结点入队
    level[rear] = k;    //更新level数组
    rear++; //尾指针后移
    while(front < rear){    //队列不为空
        p = Q[front];   //出队
        k = level[front];   //更新k
        front++;    //队头指针后移

        if(p->lchild!=NULL){    //左孩子有
            Q[rear] = p->lchild;    //左孩子入队
            level[rear] = k+1;  //孩子是在下一层了,k是记录层数的
            rear++; //队列尾指针后移
        }
        if(p->rchild!=NULL){    //右孩子有
            Q[rear] = p->rchild;    //右孩子入队
            level[rear] = k+1;  //孩子是在下一层了,k是记录层数的
            rear++; //队列尾指针后移
        }
    }   //当这个while循环执行完,就会得到一个level数组
    //遍历level数组
    int max=0, n, i=0; //记录最大的宽度,n用来计数
    k = 1;
    while (i<rear){
        n = 0;  //从0开始数数
        while (i<rear && level[i] == k){    //计数操作判断如果level[]的值是否等于这一层的层数K
            n++;    //结点计数++
            i++;    //level移动下一格
        }   //跳出循环则就是看下一层了  ,level[]的值就不等于这一层的层数K
        k++;    //去下一层
        if(n>max){
            max = n;    //记录最大的那个n
        }
    }
    return max;
}

18*、二叉树的先序非递归遍历

/*
 * 二叉树的先序非递归遍历
 * */
//思路:定义一个辅助栈,实现遍历完左子树,可以让指针回到右指针;
// ①打印根结点,把根结点压入栈;
// ②处理左子树,把左子树当作根,按照①的来做;
// ③当处理完左子树,由于我们是把根结点压入栈了,所有依次由下往上处理右孩子
void Preorder(BiTree T){
    Stack S;
    InitStack(S);
    BiTNode *p = T;

    while (p!=NULL || IsEmptyStack(S)){ //循环的条件有两个,p不为空||栈不为空
        if(p != NULL){
            printf("%d", p->data);    //打印根结点
            Push(S, p);        //把根结点压入栈中,就不怕一会进左子树回不来根结点再访问右子树了
            P = p->lchild;     //进入左子树
        }else{  //左子树遍历完了
            Pop(S, p);  //出栈一个结点,准备进入它的右子树
            p = p->rchild;  //进入出栈结点的右子树
        }
    }

}

19*、二叉树的中序非递归遍历

/*
 * 二叉树的中序非递归遍历
 * */
void Inorder(BiTree T){
    Stack S;    //辅助栈S
    InitStack(S);
    BiTNode *p = T; //遍历指针p

    while (p!=NULL || IsEmptyStack(S)) { //循环的条件有两个,p不为空||栈不为空
        if (p != NULL) {
            Push(S, p); //给它压入栈
            p = p->lchild;  //进入左子树
        } else {
            Pop(S, p);  //出栈
            printf("%d", p->data);  //打印
            p = p->rchild;  //再处理右孩子

        }
    }
}

20*、二叉树的后序非递归遍历

/*
 * 二叉树的后序非递归遍历
 * */
void Postorder(BiTree T){
    Stack S;    //辅助栈S
    InitStack(S);
    BiTNode *p = T; //遍历指针p
    BiTNode *r = NULL;  //记录上一次遍历的指针

    while (p!=NULL || IsEmptyStack(S)){
        if(p != NULL){
            Push(S, p);
            p = p->lchild;
        }else{
            GetTop(S, p);
            if(p->rchild && p->rchild !=r){
                p = p->rchild;
            }else{
                Pop(S, p);
                printf("%d", p->data);
                r = p;
                p = NULL;
            }
        }
    }
}

21、打印值为x结点的所有祖先

/*
 * 打印值为x结点的所有祖先
 * 在二叉树中查找值为x的结点,试编写算法打印值为x的结点的所有祖先,
 * 假设值为x的结点不多于一个
 * */
//后序非递归遍历的改写
void Func2(BiTree T, int x){
    Stack S;
    InitStack(S);
    BiTNode *p=T, *r=NULL;  //遍历指针p,记录指针r,记录上一次遍历的结点是谁
    while(p != NULL || IsEmptyStack(S)){
        if(p != NULL){
            Push(S, p); //压栈操作
            p = p->lchild;  //去找左子树
        }else{
            GetTop(S, p);   //指针p指向栈顶结点
            if(p->rchild && p->rchild != r){    //判断它有没有右孩子 && 是不是没有被遍历过
                p = p->rchild;
            }else{
                Pop(S, p);  //出栈
                if(p->data == x){   //如果是这个x的值的话,那就遍历打出他的祖先,就是栈里面的结点
                    while(!IsEmptyStack(S)){
                        Pop(S, p);
                        printf("%d", p->data);
                    }
                    r = p;
                    p = NULL;
                }
            }
        }
    }

}

22、线索二叉树的结构体定义

/*
 * 线索二叉树的结构体定义
 * */
typedef struct ThreadNode{
    char data;
    struct ThreadNode *lchild, *rchild;
    int ltag, rtag;    //标志域,当标志域为1时,说明指向的是该结点的前驱或者后继(先序,中序,后序序列)
                       //当标志域为0时,说明指向的是该结点的左右孩子
}ThreadNode, *ThreadTree;

 

23、二叉树的中序线索化及其中序遍历

/*
 * 二叉树的中序线索化及其中序遍历
 * 请设计一个算法构造中序线索二叉树,然后写出中序遍历算法。
 * */
//线索化代码
void InThread(ThreadNode *p, ThreadNode *pre){    //需要引用符(ThreadNode &p, ThreadNode &pre)
    if(p != NULL){
        InThread(p->lchild, pre);   //处理左子树
        if(p->lchild == NULL){  //如果左孩子为空
            p->lchild = pre;    //左孩子指针指向中序遍历前驱,正好是pre指针
            p->ltag = 1;
        }
        if(pre && pre->rchild == NULL){     //如果有pre指针域,并且其右孩子指针域为空,
            pre->rchild = p;        //那么就让让他的右孩子指针指向后继
            pre->rtag = 1;
        }
        pre = p;    //继续往下遍历
        InThread(p->rchild, pre);   //处理右子树
    }
}

ThreadTree FirstNode(ThreadTree T){ //小函数,找一下二叉树最左边结点
    while(T->ltag == 0){
        T = T->lchild;
    }
    return T;
}
ThreadTree NextNode(ThreadNode *p){ //小函数,找根结点的后继结点
    if(p->rchild == 0){
        return FirstNode(p->rchild);
    }else{
        return p->rchild;
    }
}

//中序遍历算法
void InOrder2(ThreadTree T){
    for(ThreadNode *p = FirstNode(T); p!=NULL; p= NextNode(p)){
        printf("%d", p->data);
    }
}

 

24、二叉树的先序线索化及其先序遍历

/*
 * 二叉树的先序线索化及其先序遍历
 * 请设计一个算法构造先序线索二叉树,然后写出先序遍历算法。
 * */
void PreThread(ThreadNode *p, ThreadNode *pre){
    if(p != NULL){
        if(p->lchild == NULL){
            p->lchild = pre;
            p->ltag = 1;
        }
        if(pre && pre->rchild == NULL){
            pre->rchild = p;
            pre->rtag = 1;
        }
        pre = p;
        if(p->ltag == 0){       //要加一个判断,有左右孩子才过去遍历
            PreThread(p->lchild, pre);
        }
        if(p->rtag == 0){
            PreThread(p->rchild, pre);
        }
    }
}

ThreadTree NextNode2(ThreadNode *p){
    if(p->lchild == 0){
        return p->lchild;
    }else{
        return p->rchild;
    }
}

//先序遍历函数
void PreOeder2(ThreadTree T){
    for(ThreadNode *p=T; p!= NULL; p= NextNode(p)){
        printf("%d", p->data);
    }
}

25、二叉树的后序线索化

/*
 * 二叉树的后序线索化
 * */
void PostThread(ThreadNode *p, ThreadNode *pre){
    if(p != NULL){
        PostThread(p->lchild, pre);
        PostThread(p->rchild, pre);
        if(p->lchild == NULL){
            p->lchild = pre;
            p->ltag = 1;
        }
        if(pre && pre->rchild == NULL){
            pre->rchild = p;
            pre->rtag = 1;
        }
        pre = p;
    }
}

【查找】

1、顺序查找

/*
 * 顺序查找
 * 数组A[]中有n个整数,没有次序,数组从下标1开始存储,
 * 请写出查找任一元素k的算法,若查找成功,则返回元素在数组中的位置;若不成功,则返回0
 * */
int Search(int A[], int n, int k){  //就是顺序表的遍历操作
    for(int i=1; i<=n; i++){
        if(A[i] == k){
            return i;
        }
    }
    return 0;
}
//链表的遍历 day23

2、折半查找

/*
 * 折半查找
 * 已知一个顺序表L,其中的元素递增有序排列,请分别写出折半查找的递归算法和非递归算法
 * 查找L中值为key的元素
 * */
//用折半查找时,必须是数组,顺序表,且表中元素必须有序!!!
//非递归
int BinarySearch(SeqList L, int key){
    int low=0, high=L.length-1, mid;
    while(high >= low){
        mid = (low+high)/2;
        if(L.data[mid] == key){
            return mid; //如果找到了就return了
        }
        else if(L.data[mid] > key){     //说明要去mid的左边找 让high = mid-1
            high = mid-1;
        }
        else if(L.data[mid] < key){     //说明要去mid的右边找 让low = mid+1
            low = mid+1;
        }
    }   //跳出while循环说明没找到
    return -1;
}
//递归
int BinarySearch2(SeqList L, int key, int low, int high){
    if(low > high){     //递归边界
        return -1;
    }

    int mid = (low+high)/2;
    if(L.data[mid] < key){  //说明要去mid的右边找 让low = low+1
        return BinarySearch2(L, key, mid+1, high);  //递归调用
    }
    else if(L.data[mid] > key){ //说明要去mid的左边找 让high = high-1
        return BinarySearch2(L, key, low, mid-1);  //递归调用
    }
    else{
        return mid; //找到了
    }
}

3、查找数值k并让其前移一个位置

/*
 * 查找数值k并让其前移一个位置
 * 线性表中各个结点的检索概率不等时,可用如下策略提高顺序检索的效率:
 * 若找到指定的结点,则将该结点和其前驱结点(若存在)交换,使得经常被检索的结点尽量位于表的前端。
 * 试设计在顺序结构和链式结构的线性表上实现上述策略的顺序检索算法。
 * */
//顺序表
void SeqListSearch(SeqList L, int k){   //需要 SeqList &L
    if(L.data[0] == k){ //顺序表的第一个元素就是要找的,直接结束了
        return;
    }else{
        for(int i=1; i<L.length; i++){
            if(L.data[i] == k){
                int temp;   //交换的辅助变量
                temp = L.data[i];
                L.data[i] = L.data[i-1];
                L.data[i-1] = temp;
                return;
            }
        }
    }
}
//链表
void LinkListSearch(LinkList L, int k){    //需要 LinkList &L
    if(L->next->data == k){
        return;
    }else{
        LNode *p = L;
        while (p->next->next != NULL){

            if(p->next->next->data == k){   //找到了就开始交换位置,画图理解链表的交换
                LNode *q = p->next;
                LNode *r = q->next;
                p->next = r;
                r->next = q;
                return;
            }

            p = p->next;    //找不到就继续遍历
        }
    }
}

4、二叉排序树中查找值为k结点的位置

/*
 * 二叉排序树中查找值为k结点的位置
 * 请分别写出在二叉排序树中查找数据域值为k结点位置的递归算法和非递归算法。
 * */
//非递归
BiTNode *SearchTree(BiTree T, int k){
    while(T != NULL){   //不为空时遍历
        if(T->data > k){    //根节点比k大,进入左子树找
            T = T->lchild;
        }else if(T->data < k){  //根结点比k小,进入右子树找
            T = T->rchild;
        } else{ //不大也不小,就是找到了
            return T;
        }
    }
    return NULL;
}
//递归
BiTNode *SearchTree2(BiTree T, int k){
    if(T == NULL){
        return NULL;
    } else{
        if(T->data == k){
            return T;
        }else if(T->data > k){
            return SearchTree2(T->lchild, k);
        }else if(T->data < k){
            return SearchTree2(T->rchild, k);
        }
    }
}

5、在二叉排序树中插入一个值为k的结点以及构造一棵二叉排序树

/*
 * 在二叉排序树中插入一个值为k的结点以及构造一棵二叉排序树
 * 请写出在一棵二叉排序树T中插入值为k结点的算法,
 * 插入成功则返回1,插入失败则返回0,然后写出根据数组A中存储元素值顺序构造一棵二叉排序树的算法,
 * 假设数组A的长度为n。
 * */
int BST_Insert(BiTree T, int k){    //需要BiTree &T
    if(T == NULL){      //根结点就是空的,就是找到位置插入了
        T = (BiTNode *)malloc(sizeof(BiTNode)); //申请空间
        T->data = k;
        T->lchild = NULL;
        T->rchild = NULL;
    }
    else if(T->data == k){  //二叉排序中,不允许有相同data的结点
        return 0;   //插入失败
    }
    else if(T->data > k){   //说明要去左子树中找找
        return BST_Insert(T->lchild, k);
    }
    else if(T->data < k){   //说明要去右子树中找找
        return BST_Insert(T->rchild, k);
    }
}
//把数组A插入二叉排序树
void Creat_BST(BiTree T, int A[], int n){
    T = NULL;   //初始化操作
    int i = 0;  //遍历索引
    while (i < n){
        BST_Insert(T, A[i]);    //调用上面上面函数,数组遍历的插入到二叉树中
        i++;
    }
}

6、求二叉排序树中的最小关键字和最大关键字

/*
 * 求二叉排序树中的最小关键字和最大关键字
 * 设计一个算法,求给定二叉排序树中最小和最大的关键字。
 * */
int Minkey(BiTree T){
    while (T->lchild != NULL){  //让指针不断去到树的最左下角,就是最小值了
        T = T->lchild;
    }
    return T->data;
}
int Maxkey(BiTree T){
    while (T->rchild != NULL){  //让指针不断去到树的最右下角,就是最大值了
        T = T->rchild;
    }
    return T->data;
}

7、查找指定结点在二叉排序树中的层次

/*
 * 查找指定结点在二叉排序树中的层次
 * 试编写一个算法,求出给定结点在给定二叉排序树中的层次
 * */
int LevelSearch(BiTree T, BiTNode *p){
    int n = 1;  //用来记录层次的
    while (T != p){
        if(T->data < p->data){  //如果目标值比根结点大
            T = T->rchild;  //进入右子树找找
        }else{
            T = T->lchild;  //否则进去左子树
        }
        n++;    //进入下一层都要让n++
    }
    return n;
}

8、从大到小输出二叉排序树中所有不小于k的关键字

/*
 * 从大到小输出二叉排序树中所有不小于k的关键字
 * 设计一个算法,从大到小输出二叉排序树中所有值不小于k的关键字
 * */
//思路:改写中序递归遍历的算法(左根右)——>(右根左)
void Func(BiTree T, int k){
    if(T != NULL){  //如果根不是空的
        Func(T->rchild, k);     //先递归遍历右子树
        if(T->data >= k){
            printf("%d", T->data);
        }
        Func(T->lchild, k); //继续递归调用左子树
    }
}

9、判断一棵二叉树是否为二叉排序树

/*
 * 判断一棵二叉树是否为二叉排序树
 * 试编写一个算法,判断给定的二叉树是否为二叉排序树
 * */
//思路:二叉排序树中序遍历后得到的是一个递增的序列,前边的一定比后边的小,否则就不是二叉排序树
int pre = 0; //这个0定义为所有整数中最小的数字
int JudgeBST(BiTree T, int pre){
    int b1, b2; //记录左子树是不是二叉排序树,右子树是不是二叉排序树
    if(T == NULL){
        return 1;   //如果是个空树,那他一定是二叉排序树
    }else{
        b1 = JudgeBST(T->lchild, pre);  //看看左子树是不是二叉排序树
        if(b1 == 0 || T->data <= pre){  //如果b1是0,那就不是二叉排序树,还有如果遍历的数值比pre还小那也不是二叉排序树
                                        // (pre是中序遍历中记录遍历到的数值的前驱的数值,第一轮是中序遍历数组最前边的,也就是最小的数字)
            return 0;   //那就不是二叉排序树,甚至不用看右子树
        }
        pre = T->data;  //更新为中序遍历的前驱
        b2 = JudgeBST(T->rchild, pre);
        return b2;  //如果b2是二叉排序树,那整个就是
    }
}

10、判断一棵二叉排序树是否为平衡二叉树

/*
 * 判断一棵二叉排序树是否为平衡二叉树
 * 试编写一个判断二叉排序树是否是平衡二叉树的算法。
 * */
int Func2(BiTree T){
    if(T == NULL){
        return 0;
    }else{
        int left = Func2(T->lchild);    //判断左子树是否是平衡二叉树
        int right = Func2(T->rchild);   //判断右子树是否为平衡二叉树
        if(left == -1 || right == -1 || abs(left-right)>1){ //三个条件,左子树不是平衡二叉树,右子树不是平衡二叉树,平衡因子大于1
            return -1;  //那他们都不是平衡二叉树;【注】abs是绝对值函数
        }else{
            return max(left, right)+1;  //是平衡二叉树就返回高度,不是就返回-1
        }
    }
}
int JudgeAVL(BiTree T){ //判断是否是平衡二叉树
    if(Func2(T) == -1){
        return 0;
    }else{
        return 1;
    }
}

11、根据lsize域查找平衡二叉树中的第k小结点位置

/*
 * 根据lsize域查找平衡二叉树中的第k小结点位置
 * 在平衡二叉树的每个结点中增设一个域lsize,存储以该结点为根的左子树中的结点个数加一。
 * 试编写一个算法,确定树中第k小结点的位置。
 * */
//思路:中序遍历中,第k个结点
BiTree *Search_k(BiTree T, int k){
    if(T == NULL || k<1){   //空树或者第k个结点,k<1说明找不到的
        return NULL;
    }else if(T->lsize == k){    //此处引入lsize,平衡二叉树的中序遍历下的序列,每个结点是第lsize个结点
        return T;   //如果根结点的lsize就是k,那就直接ruturn
    }
    else if(T->lsize > k){  //根结点的lsize比k大,那就要去左子树找
        return Search_k(T->lchild, k);
    }else { //根结点的lsize比k小,那就要去右子树中找
        return Search_k(T->rchild, k - T->lsize); //其中右子树的第一个结点是在右子树的左下角
    }
}

【排序】

1、直接插入排序

/*
 * 直接插入排序
 * 请分别写出顺序存储和链式存储的直接插入排序算法
 * */
//顺序存储
void InsertSort(int A[], int n){
    int i, j;
    for(i = 2; i<=n; i++){  //顺序表中,下标为0的为暂存空间,下标为1的,只有一个数据,默认已经排序了
        if(A[i] < A[i-1]){  //如果待排序的 比 前面已经排好序的最后一个元素小,那就是要找位置插入了
            A[0] = A[i];    //先把待排序的放入A[0]暂存
            for(j=i-1; A[0]<A[j]; j--){ //找到第一个比待排序数值大的,这里的j--和--j没区别,都是指到了第一个不比A[0]大的位置
                A[j+1] = A[j];  //把第一个大的往后移一位
            }
            A[j+1] = A[0];  //把A[0]插入第一个比他大的数值的前面
                            //当A[0]与每一个大于它的比较之后,大于它的都会往后移动一格,
                            // 然后比到比它小的时候,for循环就退出了,
                            // 此时j的位置在比它小的那个格子上,我们要插入在比它小的后一格,比它的都往后移动一个了,所以刚好空出一个位置
        }
    }
}
//链式存储
//思路:将链表一分为二,已排序,待排序
void InsertSort2(LinkList L){   //需要LinkList &L
    if(L->next == NULL){    //只有一个结点的话,没办法分成两部分了
        return;
    }else{
        LNode *pre,*q,*p,*r;    //*pre和*q是已经拍好序的这边链表;*p和*r是待排序用来遍历的

        p = L->next->next;  //先用指针指着,防止真的找不到待排序链表了
        L->next->next=NULL; //一刀切

        while(p != NULL){   //待排序,p的后面还有呢
            pre = L;    //记录已排序链表中q指针的前驱
            q = pre->next;  //用作遍历

            while(q != NULL && q->data < p->data) {  //只有当q遍历到已排序的尾巴,或者q的数值比p的数值大的时候才会停下来
                pre = q;
                q = q->next;
            }   //找到要插入的位置
            r = p->next;    //p要插过去,先让r记录下一个待排序,防断链
            p->next = q;    //此时的q是比p大的,所以插到q前面
            pre->next = p;  //也就是p的前驱也改掉
            p = r;  //p回到待排序链表
        }
    }
}

2、将前后两部分分别有序的数组调整为整体有序

/*
 * 将前后两部分分别有序的数组调整为整体有序
 * 有一个数组A存储着m+n个整型元素,元素从下标1开始存储,其前m个元素递增有序,后n个元素递增有序,
 * 请设计一个算法使这个数组整体有序
 * */
void InsertSort3(int A[], int m, int  n){
    int i, j;
    for(i=m+1; i<=m+n; i++){    //就是直接插入排序,直接插入排序从下标为2开始比较,
                                // 本题中给定了条件前m个有序,从m+1开始比较
        if(A[i] < A[i-1]){
            A[0] = A[i];
            for(j=i-1; A[0]<A[j]; j--){
                A[j+1] = A[j];
            }
            A[j+1] = A[0];
        }
    }
}

3、折半插入排序

/*
 * 折半插入排序
 * */
//【注】与直接插入排序(待排序的与已排序的挨个对比)不同,折半插入排序在对比时,对已排序数组进行折半查找
void InsertHalfSort(int A[], int n){
    int i, j, low, high, mid;
    for(i=2; i<=n; i++){
        A[0] = A[i];    //放入暂存位置
        low = 1;
        high = i-1;
        while(low <= high){ //对已排序数组折半查找
            mid = (low+high)/2;
            if(A[i] < A[mid]){  //如果 [待排序的数值] 比 [已排序的mid] 小
                high = mid-1;   //说明要去mid的左边找
            } else{
                low = mid+1;    //否则去mid的右边找
            }   //直到low > high退出while循环,
        }
        for(j = i-1; j>=low; j--){  //遍历,让low以及后面的元素往后移动一格
            A[j+1] = A[j];
        }
        A[low] = A[0];  //low的位置插入元素A[0]
    }
}

4、希尔排序

/*
 * 希尔排序
 * */
//【考试一般不做代码要求】
void ShellSort(int A[], int n){
    int d,i,j;  //步长d
    for(d=n/2; d>=1; d=d/2){
        for(i=1+d; i<=n; i++){
            if(A[i] < A[i-d]){
                A[0] = A[i];
                for(j=i-d; j>0 && A[0]<A[j]; j=j-d){
                    A[j+d] = A[j];
                }
                A[j+d] = A[j];
            }
        }
    }
}

5、冒泡排序

/*
 * 冒泡排序
 * */
void BubbleSort(int A[], int n){
    int i, j, temp; //i记录现在第几次排序,j是从后往前的一个索引,temp辅助交换变量
    int flag;   //标志域,记录此次循环中,有没有发生交换【优化】没有交换说明整体有序了,可以直接退出,不需要执行n-1次
    for(i=0; i<n-1; i++){   //每一趟只能确定一个结点的最终位置,有n-1个结点
        flag = 0; //每次冒泡之前让他等于0,发生交换了再等于1
        for(j=n-1; j>i; j--){   //从后往前冒泡
            if(A[j-1] > A[j]){
                temp = A[j];
                A[j] = A[j-1];
                A[j-1] = temp;
                flag = 1;   //发生了交换,flag置1
            }
        }
        if(flag == 0){
            return;
        }
    }
}

6、双向冒泡排序

/*
 * 双向冒泡排序
 * 编写双向冒泡排序算法,在正反两个方向交替进行扫描,
 * 即第一趟把关键字最大的元素放在序列的最后面,
 * 第二趟把关键字最小的元素放在序列的最前面,如此反复
 * */
void DoubleBubbleSort(int A[], int n){
    int low=0, high=n-1;
    int temp,flag;
    while(low < high && flag==1){   //当low>=high,标志域=0没发生交换时,退出循环
        flag = 0;
        for(int i=low; i<high; i++){    //low从左往右遍历
            if(A[i] > A[i+1]){  //如果A[i]比A[i+1]大,说明位置反了
                temp = A[i+1];
                A[i] = A[i+1];
                A[i+1] = temp;
                flag = 1;   //发生了交换
            }
        }   //这个for循环结束后会有一个最大元素到了序列最后面
        high--;
        for(int i=high; i>low; i--){    //反过来,找到最小的放在序列前面
            if(A[i] < A[i-1]){
                temp = A[i];
                A[i] = A[i-1];
                A[i-1] = temp;
                flag = 1;
            }
        }
        low++;
    }
}

7*、快速排序

【注】每趟排序都会有一个元素到最终位置,其左边元素比它小,右边元素比它大

/*
 * 快速排序
 * */
//划分的代码***【重点】
int Partition(int A[], int low, int high){
    int pirot = A[low]; //基准元素
    while (low < high) {
        while (low < high && A[high] >= pirot) {
            high--;
        }
        A[low] = A[high];   //不满足while循环条件,交换low high的值
        while (low < high && A[low] <= pirot) {
            low++;
        }
        A[high] = A[low];   //不满足while循环条件,交换low high的值
    }
    A[low] = pirot; //最终找到了基准元素该在的地方,此时A[low] = A[high]
    return low; //返回出最终位置
}


//快排
void QuickSort(int A[], int low, int high){
    if(low<high){   //递归边界
        int pirotpos = Partition(A,low,high);   //基准元素到达了最终位置,它的左右划分成了两块
        QuickSort(A, low, pirotpos-1);  //左边的部分 low还是low,high是基准元素位置-1
        QuickSort(A, pirotpos+1, high); //右边部分,low是基准元素+1,high还是high
    }
}

8、将数组最后一个元素放到整体有序后的正确位置

/*
 * 将数组最后一个元素放到整体有序后的正确位置(快排的划分算法)
 * 有一个整型数组A存储了n个元素,其元素序列是无序的,元素从下标为1开始存储,
 * 请设计一个算法让数组A中的最后一个元素放在数组A整体排序后的正确位置上,
 * 结果返回其数组下标,要求关键字的比较次数不超过n
 * */
int Partition2(int A[], int n){
    int low=1, high=n;
    int pirot = A[high];    //选取基准元素
    while (low < high){
        while (low < high && A[low] <= pirot){
            low++;
        }
        A[high] = A[low];
        while (low < high && A[high] >= pirot){
            high--;
        }
        A[low] = A[high];
    }
    A[low] = pirot;
    return low;
}

9*、找出数组中第k小的元素

【思路】利用了快排的特性,每趟排序都会有一个元素到最终位置

/*
 * 找出数组中第k小的元素(快排的划分算法)(*重点)
 * 请设计一个尽可能高效的算法,使之能够在数组A[1...n]中找出第k小的元素
 * 即从小到大排序后处于第k个位置的元素
 * */
int Search_k(int A[], int low, int high, int k){
    int pirot = A[low];
    int low_temp=low, high_temp=high;   //记录原始地址的low,high在哪,避免划分成小块后的low不是原序列的low
    while (low < high){
        while (low < high && A[high] >= pirot){
            high--;
        }
        A[low] = A[high];
        while (low < high && A[low] <= pirot){
            low++;
        }
        A[high] = A[low];
    }

    A[low] = pirot;
    if(low == k){   //如果划分一次刚好就是,就直接return了
        return A[low];
    }
    else if(low > k){   //说明还要去左边找
        return Search_k(A, low_temp, low-1, k); //此时的low就是pirot的基准位置,不是最开始的low,所以用low_temp
    }
    else if(low < k){
        return Search_k(A, low+1, high_temp, k);
    }
}

10、把数组中所有奇数移动到所有偶数的前面

/*
 * 把数组中所有奇数移动到所有偶数的前面
 * 已知一个数组A存储了n个元素,且数组中每个元素都是不相同的正整数,
 * 请设计一个高效算法把数组A中所有奇数移动到所有偶数前边的算法
 * */
//思路:从左边往右找到第一个奇数,从右往左找到第一个偶数,交换位置
void Move(int A[], int n){
    int low=0, high=n-1, temp;
    while (low<high){
        while (low<high && A[low]%2 != 0){  //找到奇数的位置下标low
            low++;
        }
        while (low<high && A[high]%2 != 1){ //找到偶数的位置下标high
            high--;
        }
        if(low<high){   //防止low 和 high指向同一个位置,就可以不用交换了,(加不加也无所谓)
            temp = A[low];  //交换low high的数值
            A[low] = A[high];
            A[high] = temp;
            low++;
            high--;
        }
    }
}

11、荷兰旗问题

/*
 * 荷兰旗问题
 * 顺序放置n个球,每个球的颜色是红,白,蓝之一,
 * 要求重新排列这些球,使得所有红色的球在前,白色球居中,蓝色球在后。
 * 假设放置球的序列存放在数组A中,0代表球为红色,1代表球为白色,2代表球为蓝色,
 * 请设计一个算法为这n个球排序
 * */
//思路:利用交换算法,让low的左边为红色,high的右边为蓝色,
// i用来在low到high中间遍历,遍历到什么颜色就跟low或者high交换,然后在low++,high--。(遍历到白色不动)
void HeLanSort(int A[], int n){
    int low=0, high=n-1, i=0;
    while (i <= high){

        if(A[i] == 0){  //如果是红色,跟low交换,low++,i遍历访问下一个位置
            Swap(A[i] , A[low]);
            low++;
            i++;
        }
        else if(A[i] == 1){ //如果是白色,就没有交换操作,i遍历访问下一个位置
            i++;
        }
        else if(A[i] ==2){  //如果是蓝色,跟high交换,high--,【注】i不要遍历,防止交换i和high交换回来的是个0,应该去前面
            Swap(A[i], A[high]);
            high--;
        }

    }
}

12、简单选择排序

/*
 * 简单选择排序
 * */
//顺序存储
void SelectSort(int A[], int n){
    int i,j,min,temp;   //i记录哪个位置的最终元素,j遍历索引,min最小值的位置,temp交换辅助
    for(i=0; i<n-1; i++){
        min = i;    //初始化索引
        for(j=i+1; j<n; j++){   //这个for循环找到个最小值
            if(A[j] < A[min]){
                min = j;    //j遍历的最小值的下标更新给min
            }
        }
        temp = A[i];    //交换操作,选择排序就是遍历找到最小的交换放在前面
        A[i] = A[min];
        A[min] = temp;
    }
}
//链式存储
//思路:找最大值,头插法
void SelectSort2(LinkList L){   //需要LinkList &L
    LNode *pre, *p, *maxpre, *maxp;
    LNode *r = L;   //用来记录已排序好的最后一个结点的指针
    while(r->next != NULL){
        pre = r;    //初始化
        p = r->next;    //r是已排序的最后一个,r的下一个是待排序
        maxpre = pre;
        maxp = p;
        while (p != NULL){  //遍历指针不为为就继续往下遍历
            if(p->data > maxp->data){   //找到更大的更新pre,maxpre
                maxpre = pre;
                maxp = p;
            }
            pre = p;
            p = p->next;    //往下遍历
        }
        maxpre->next = maxp->next;  //把最大值结点拿出来
        maxp->next = L->next;   //头插法
        L->next = maxp;
        if(r == L){ //如果r还在头结点,那就更新r
            r = maxp;
        }
    }
}

13、计数排序

/*
 *  计数排序
 *  有一种简单的排序算法称为计数排序
 *  其算法思想是选取一个待排序的元素值,然后遍历整个数组,统计数中有多少个元素比选取的待排序数组小,
 *  假设统计出的计数值为c,则可以根据计数值c在新的有序数组中找到待排序元素的最终位置。
 *  现有一个数组A,存放了n个互不相同的整型元素,请使用计数排序算法为数组A排序,排序结果存放在数组B中。
 * */
void Count(int A[], int B[], int n){
    int i, j;
    int count;  //负责计数使用
    for(i=0; i<n; i++){ //i是处理第几个位置的元素
        for(j=0, count=0; j<n; j++){    //遍历整个数组找到多少个小的
            if(A[j] < A[i]){
                count++;
            }
        }
        B[count] = A[i];
    }
}

14、堆排序

/*
 * 堆排序
 * 有一个数组A存储了n个整型元素,元素从数组下标1开始存储,请写出对数组A使用堆排序的算法。
 * */
//堆的调整代码
void HeapAdjust(int A[], int n, int k){     //k代表要调整元素的下标
    A[0] = A[k];
    int i = 2*k;
    while(i <= n){
        if(i<n && A[i+1]>A[i]){ //先看左孩子右孩子哪个比较大,用大的跟根结点比
            i++;    //来到右孩子这边
        }
        if(A[i] > A[0]){
            A[k] = A[i];    //比调整元素(爹)大,那就要上去,跟爹换一下位置
            k = i;  //交换完了之后,待调整元素就要和爹交换的那个结点位置走了
            i = 2*i;
        }else{
            break;  //证明已经调整到位
        }
    }
    A[k] = A[0];
}
//构造大根堆
void BuildMaxHeap(int A[], int n){
    for(int i=n/2; i>0; i--){
        HeapAdjust(A, n, i);
    }
}
//堆排序算法(处理数组交换的部分)
void HeapSort(int A[], int n){
    BuildMaxHeap(A, n);
    int temp;
    for(int i=n; i>1; i--){
        temp = A[1];
        A[1] = A[i];
        A[i] = temp;
        HeapAdjust(A, i-1, 1);
    }
}

15、判断一个数组是否构成小根堆

/*
 * 判断一个数组是否构成小根堆
 * 有一个数组A存储了n个整型元素,元素从数组下标为1开始存储,
 * 试设计一个算法判断数组A是否构成一个小根堆
 * */
int JudgeMinHeap(int A[], int n){
    if(n%2 == 0){   //为偶数的情况
        if(A[n/2]>A[n]){
            return 0;
        }
        for(int i=n/2-1; i>0; i--){
            if(A[i]>A[2*i] || A[i]>A[2*i+1]){   //根大于左右就不是小根堆了
                return 0;
            }
        }
    }else{  //为奇数的情况
        for(int i=n/2; i>0; i--){
            if(A[i]>A[2*i] || A[i]>A[2*i+1]){   //根大于左右就不是小根堆了
                return 0;
            }
        }
    }
    return 1;
}

16、二路归并排序算法

/*
 * 二路归并排序算法
 * */
//调整算法
void Merge(int A[], int low, int mid, int high){    //low整个数组最左边,high整个数组最右边
    int n1 = mid-low+1; //左边数组有多少
    int n2 = high-mid;  //右边数组有多少
    int *L=(int*)malloc(sizeof (int)*n1);
    int *R=(int*)malloc(sizeof (int)*n2);
    int i, j, k;
    for(i=0, k=low; k<=mid; i++,k++){
        L[i] = A[k];    //把整体左边序列复制到新数组L
    }
    for(j=0, k=mid+1; k<=high; j++, k++){
        R[i] = A[k];    //把整体右边序列复制到新数组R
    }
    for(i=0,j=0,k=low; i<n1 && j<n2; k++){
        if(L[i] < R[j]){    //哪个大放回哪个
            A[k] = L[i++];
        }else{
            A[k] = R[j++];
        }
    }
    //还没有完,有可能一个数组消耗完了,另一个没完的情况
    while(i<n1){
        A[k++] = L[i++];
    }
    while(j<n2){
        A[k++] = R[j++];
    }
}
//排序算法
void MergeSort(int A[], int low, int high){
    if(low<high){
        int mid = (low+high)/2; //数组一分为二
        MergeSort(A, low, mid); //递归左边
        MergeSort(A, mid+1, high);  //递归右边
        Merge(A, low, mid, high);   //给他们排好序
    }
}

【图】

1、图的邻接矩阵存储结构体定义及其基本操作

/*
 * 图的邻接矩阵存储结构体定义及其基本操作
 * */
typedef struct MGraph{
    char Vex[MaxSize];  //记录顶点
    int Edge[MaxSize][MaxSize];     //二维数组,邻接矩阵,记录边
    int vexnum, arcnum; //记录顶点数量,边的数量
}MGraph;

//一些基本操作
int Func(MGraph G){
    int a = G.vexnum;   //取一下顶点数
    int b = G.arcnum;   //取一下边数
    printf("%d", G.Vex[0]); //输出一下顶点A,(结合图像来理解,此时A存放在数组Vex第一个位置,下标为0)
    if(G.Edge[0][1] == 0){  //判断A到B是否有路径(结合矩阵来理解)
        return 0;
    }
}

2、图的邻接表存储结构体定义及其基本操作

/*
 * 图的邻接表存储结构体定义及其基本操作
 * */
typedef struct ArcNode{     //边表
    int adjvex;     //顶点的下标
    struct ArcNode *nextarc;    //指向下一个边
}ArcNode;

typedef struct VNode{   //顶点结点
    char data;  //数据域
    ArcNode *firstarc;  //第一个边
}VNode;

typedef struct AGraph{      //邻接表图的定义
    VNode adjlist[MaxSize]; //邻接表
    int vexnum, arcnum;     //顶点的个数,边的个数
}AGraph;

//一些基本操作
void Func2(AGraph *G){  //【注】传入的指针变量
    int a = G->vexnum;  //取一下顶点数
    int b = G->arcnum;  //取一下边数
    printf("%c", G->adjlist[0].data);    //打印数据域的值
    ArcNode *p;     //定义了一个遍历指针
    p = G->adjlist[0].firstarc; //初始化,指向第一个边角结点,而不是顶点结点
    printf("%c", G->adjlist[p->adjvex].data);    //打印第一个边角结点的数据值
    while(p != NULL){   //遍历顶点A指向的所有的边
        p = p->nextarc;
    }
}

3、图的邻接表存储转换成邻接矩阵存储

/*
 * 图的邻接表存储转换成邻接矩阵存储
 * */
void Func3(MGraph G1, AGraph *G2){      //需要MGraph &G1
    G1.vexnum = G2->vexnum;     //这两个数值是相同的,直接复制过来
    G1.arcnum = G2->arcnum;

    for(int i=0; i<G1.vexnum; i++){    //让邻接矩阵初始化,全部置0先
        for(int j=0; j<G1.vexnum; j++){
            G1.Edge[i][j] = 0;
        }
    }

    ArcNode *p;
    for(int i=0; i<G2->vexnum; i++){    //遍历邻接表,把东西搬到邻接矩阵
        G1.Vex[i] = G2->adjlist[i].data;  //邻接表的数据域ABCD存到邻接矩阵的数组ABCD(结合图来理解)
        p = G2->adjlist[i].firstarc;
        while (p != NULL){
            G1.Edge[i][p->adjvex] = 1;  //邻接表的一个顶点指向所有边复制给G1,没有的时候i++,下一个顶点
            p = p->nextarc;    //继续遍历
        }
    }
}

4、图的邻接矩阵存储转换成邻接表存储

/*
 * 图的邻接矩阵存储转换成邻接表存储
 * */
void Func4(MGraph G1, AGraph *G2){
    G2->vexnum = G1.vexnum;     //这两个数值是相同的,直接复制过来
    G2->arcnum = G1.arcnum;

    for(int i=0; i<G1.vexnum; i++){
        G2->adjlist[i].data = G1.Vex[i];
        G2->adjlist[i].firstarc = NULL;   //初始化
    }

    ArcNode *p;
    for(int i=0; i<G1.vexnum; i++){     //邻接矩阵遍历的代码
        for(int j=0; j<G1.vexnum; j++){
            if(G1.Edge[i][j] !=0 ){ //证明有边
                p = (ArcNode *)malloc(sizeof (ArcNode));    //创建边角结点
                p->adjvex = j;  //顶点下标赋值
                p->nextarc = G2->adjlist[i].firstarc;   //新结点指向下一个的指为空(跟初始的顶点结点指Null一样),用头插法插入邻接表
                G2->adjlist[i].firstarc=p;  //指过去
            }
        }

    }
}

5*、广度优先遍历BFS(邻接矩阵形式存储图)

/*
 * 邻接矩阵形式存储图的广度优先遍历(BFS)
 * */
//类似树的层次遍历,借助队列来辅助,还需要判断它是否被遍历过,定义的辅助数组记录是否被访问过
void BFS(MGraph G, int v, int visited[]){  //从v下标开始广度优先遍历
    Queue Q;
    InitQueue(Q);
    printf("%c", G.Vex[v]);
    visited[v] = 1;     //访问过了,辅助数组对应位置 置1
    while (!IsEmpty(Q)){    //当队列不是空
        DeQueue(Q, v);  //出队一个
        for(int j=0; j<G.vexnum; j++){  //遍历它的有多少条指出去的边
            if(G.Edge[v][j] == 1 && visited[j] == 0){   //有边 而且 没被访问过
                printf("%c", G.Vex[j]);
                visited[j] = 1;
                EnQueue(Q, j);  //还要入队,因为要访问它们的指向的边,由于辅助数组置1,他不会再次打印
            }
        }
    }
}
//处理非连通图的情况
void Func5(MGraph G, int v){
    int visited[G.vexnum];
    for(int i=0; i<G.vexnum; i++){
        visited[i] = 0;
    }
    BFS(G, v, visited);
    for(int i=0; i<G.vexnum; i++){
        if(visited[i] == 0){
            BFS(G, i, visited);
        }
    }
}

6*、广度优先遍历BFS(邻接表形式存储图)

/*
 * 邻接表形式存储图的广度优先遍历(BFS)
 * */
void BFS2(AGraph *G, int v, int visited[]){
    Queue Q;
    InitQueue(Q);   //借助队列

    printf("%c", G->adjlist[v].data);   //打印
    visited[v] = 1;             //更新访问过情况
    EnQueue(Q, v);      //打印了之后,入队
    ArcNode *p;                 //遍历指针
    while (!IsEmpty(Q)){    //队列不为空的时候
        DeQueue(Q, v);  //出队一个
        p = G->adjlist[v].firstarc; //遍历这一顶点的这一排边角结点
        while (p != NULL){      //p遍历不为空
            if(visited[p->adjvex] == 0){    //没访问过
                printf("%c", G->adjlist[p->adjvex].data);   //打印
                visited[p->adjvex] = 1; //遍历数组更新
                EnQueue(Q, p->adjvex);  //入队
            }
            p = p->nextarc; //下一个顶点
        }
    }
}
//处理非连通图的情况
void Func6(AGraph *G, int v){
    int visited[G->vexnum];
    for(int i=0; i<G->vexnum; i++){
        visited[i] = 0;
    }
    BFS2(G, v, visited);
    for(int i=0; i<G->vexnum; i++){
        if(visited[i] == 0){
            BFS2(G, i, visited);
        }
    }
}

7、找出邻接表存储图中距离顶点v最远的顶点

/*
 * 找出邻接表存储图中距离顶点v最远的顶点
 * 设计一个算法,找出邻接表方式存储的无向连通图G中距离顶点v最远的一个顶点。
 * 所谓最远就是到达v的路径长度最长
 * */
//思路:广度优先遍历的最后一层
int Func7(AGraph *G, int v, int visited[]){
    for(int i=0; i<G->vexnum; i++){
        visited[i] = 0;     //初始化操作
    }
    Queue Q;
    InitQueue(Q);       //初始化

    visited[v] = 1;
    EnQueue(Q, v);
    ArcNode *p;     //遍历指针
    while(!IsEmpty(Q)){ //队列不为空
        DeQueue(Q, v);  //出队
        p = G->adjlist[v].firstarc; //指向第一个边角结点,然后遍历这一排
        while(p != NULL){
            if(visited[p->adjvex] == 0){    //没有被遍历过
                visited[p->adjvex] = 1;     //现在给他遍历过
                EnQueue(Q, p->adjvex);  //放入队
            }
            p = p->nextarc; //p往下一个顶点
        }
    }
    return  v;      //最后一个出队的就是最远的
}

8*、邻接表存储图的单源最短路径问题

/*
 * 邻接表存储图的单源最短路径问题
 * 请写出利用BFS算法求解邻接表存储的图中单源最短路径的算法
 * */
//定义一个距离数组d来记录路径的长度
void BFS_MIN_Distance(AGraph *G, int v, int visited[], int d[]){
    for(int i=0; i<G->vexnum; i++){
        visited[i] = 0;
        d[i] = INT_MAX;     //INT_MAX是很大的一个值,无穷
    }
    Queue Q;
    InitQueue(Q);
    ArcNode *p;
    visited[v] = 1;     //第一个访问置1
    d[v] = 0;   //源头到源头没有距离
    EnQueue(Q, v);
    while (!IsEmpty(Q)){
        DeQueue(Q, v);
        p = G->adjlist[v].firstarc;
        while (p != NULL){
            if(visited[p->adjvex] == 0){    //还没遍历过
                visited[p->adjvex] = 1;     //现在遍历
                d[p->adjvex] = d[v] + 1;    //源头到这个顶点的距离
                EnQueue(Q, p->adjvex);  //入队
            }
            p = p->nextarc;     //p还要继续往下遍历
        }
    }
}

9*、深度优先遍历DFS(邻接矩阵形式存储图)

/*
 * 邻接矩阵形式存储图的深度优先遍历(DFS)
 * */
void DFS(MGraph G, int v, int visited[]){
    printf("%c", G.Vex[v]);     //先打印一点
    visited[v] = 1;     //访问过置1
    for(int j=0; j<G.vexnum; j++){  //遍历矩阵的这一行
        if(G.Edge[v][j]==1 && visited[j]==0){   //找到第一个顶点,而且没被访问过
            DFS(G, j, visited);     //这就以这点为基准,访问这个顶点那一排的顶点,找到第一个没被访问过的,递归下去
        }
    }
}
//处理非连通图的情况
void Func8(MGraph G, int v){
    int visited[G.vexnum];
    for(int i=0; i<G.vexnum; i++){
        visited[i] = 0;
    }
    DFS(G, v, visited);
    for(int i=0; i<G.vexnum; i++){
        if(visited[i] == 0){
            DFS(G, i, visited);
        }
    }
}

10、深度优先遍历DFS(邻接表形式存储图)

/*
 * 邻接表形式存储图的深度优先遍历
 * */
void DFS2(AGraph *G, int v, int visited[]){
    printf("%c", G->adjlist[v].data);   //打印第一个
    visited[v] = 1; //遍历过置1
    ArcNode *p = G->adjlist[v].firstarc;    //找第一个边角结点的遍历指针p
    while (p != NULL){
        if(visited[p->adjvex] == 0){    //如果没被访问过
            DFS2(G, p->adjvex, visited);    //递归访问第一个边角结点(以该顶点为基础),再找它的第一个边角结点,递归下去
        }
        p = p->nextarc; //继续往下
    }
}
//处理非连通图的情况
void Func9(AGraph *G, int v){
    int visited[G->vexnum];
    for(int i=0; i<G->vexnum; i++){
        visited[i] = 0;
    }
    DFS2(G, v, visited);
    for(int i=0; i<G->vexnum; i++){
        if(visited[i] == 0){
            DFS2(G, i, visited);
        }
    }
}

11、判断邻接表存储的图中顶点i到顶点j有无路径

/*
 * 判断邻接表存储的图中顶点i到顶点j有无路径
 * 有向图G以邻接表方式存储,请设计一个算法判断图G中顶点i到顶点j是否存在路径。(i和j不相等)
 * */
//从顶点i开始广度或者深度遍历,一趟之后看是否遍历到
int Path_i_j(AGraph *G, int i, int j){
    int visited[G->vexnum];
    for(int k=0; k<G->vexnum; k++){
        visited[k] = 0;     //初始化遍历数组
    }
    DFS2(G, i, visited); //  或者BFS(G, i, visited);   深度广度遍历都行
    if(visited[j] == 1){
        return 1;        //如果顶点j在遍历数组中为1,意思就是被访问过了,那就是被访问过了,有路径
    }else{
        return 0;
    }
}

12、计算邻接表存储的无向图中有几个连通分量

/*
 * 计算邻接表存储的无向图中有几个连通分量
 * */
int Func10(AGraph *G){
    int visited[G->vexnum];
    for(int i=0; i<G->vexnum; i++){
        visited[i] = 0; //初始化遍历数组
    }
    int count = 0;  //用来计数的,初始化
    for(int i=0; i<G->vexnum; i++){ //遍历【遍历数组】
        if(visited[i] == 0){        //找一个顶点没访问过的,给他进行广度深度遍历,同时count++ ,
            // 下一趟之后,同一个连通分量的都会访问过,再遍历【遍历数组】中下一个visited[i]==0的就是另一个连通分量
            DFS2(G, i, visited); //或者BFS(G, i, visited);    用什么遍历都可以的
            count++;
        }
    }
    return count;
}

13、判断邻接表存储的无向图是否是一棵树

/*
 * 判断邻接表存储的无向图是否是一棵树
 * */
//要求:①图是连通的;②边和顶点的关系,边的数量 = 顶点的数量-1
int IsTree(AGraph *G){
    int visited[G->vexnum];
    for(int i=0; i<G->vexnum; i++){
        visited[i] = 0;     //初始化遍历数组
    }
    DFS2(G, 0, visited);        //同样的也可以用广度遍历BFS,默认从顶点0开始
            //由于进行了一次遍历,如果是连通图的话,那么图里的结点都会访问过的,遍历数组都会置1
    for(int i=0; i<G->vexnum; i++){
        if(visited[i] == 0){
            return 0;       //判断一下,遍历数组还有为0的话就不是连通图
        }
        if(G->arcnum == G->vexnum-1){
            return 1;   //上面的条件满足是一个连通图后,如果还满足:边的数量 = 顶点的数量-1, 那就是一棵树
        } else{
            return 0;
        }

    }
}

14、拓扑排序

/*
 * 拓扑排序
 * */
typedef struct VNode2{
    char data;
    int indegree;   //入度
    ArcNode *firstarc;
}VNode2;

int Top(AGraph *G){
    int i=0, count=0;
    ArcNode *p;
    Stack S;
    InitStack(S);
    for( ; i<G->vexnum; i++){
        if(G->adjlist[i].indegree == 0){
            Push(S, i);
        }
    }
    while(!IsEmptyStack(S)){
        Pop(S, i);
        printf("%c", G->adjlist[i].data);
        count++;
        p = G->adjlist[i].firstarc;
        while (p != NULL){
            G->adjlist[p->adjvex].indegree--;
            if(G->adjlist[p->adjvex].indegree == 0){
                Push(S, p->adjvex);
            }
            p = p->nextarc;
        }
    }
    if(count == G->vexnum){
        return 1;
    }else{
        return 0;
    }
}

15、判断一个无向图是否有环

/*
 * 判断一个无向图是否有环
 * */
//临界条件:边的数量 = 顶点个数-n个连通分量
//判断有几个连通分量的代码
int Func11(AGraph *G){
    int visited[G->vexnum];
    for(int i=0; i<G->vexnum; i++){
        visited[i] = 0;
    }
    int count = 0;
    for(int i=0; i<G->vexnum; i++){
        if(visited[i] == 0){
            DFS2(G, i, visited);
            count++;
        }
    }
    return count;
}
//判断有无环
int IsLoop(AGraph *G){
    int n = Func11(G);  //连通分量赋值
    if(G->arcnum == G->vexnum-n){   //满足:边的数量 = 顶点个数-n个连通分量,则就没有环
        return 0;
    } else{
        return 1;
    }
}

16、Prim算法

/*
 * Prim算法
 * 构造最小生成数的Prim算法
 * */
void Prim(MGraph G, int v){
    int visited[G.vexnum];
    int lowcost[G.vexnum];
    for(int i=0; i<G.vexnum; i++){
        visited[i] = 0;     //初始化
        lowcost[i] = G.Edge[v][i];  //遍历矩阵的第v行遍历进数组lowcost中
    }
    printf("%c", G.Vex[v]); //先默认打印出第一个
    visited[v] = 1; //访问过就遍历数组置1
    int min, k;     //记录最小值的权值,位置
    for(int i=0; i<G.vexnum-1; i++){    //找权值最小的边
        min = INT_MIN;
        for(int j=0; j<G.vexnum; j++){  //没访问过且没加入树
            if(visited[j] == 0 && lowcost[j]<min){
                min = lowcost[j];
                k = j;  //记录顶点位置
            }
        }
        printf("%c", G.Vex[k]); //打印
        visited[k] = 1;     //加入到最小生成树中
        for(int j=0; i<G.vexnum; j++){  //更新下lowcost数组,由于新结点的加入,最短距离有所改变
            if(visited[j] == 0 && G.Edge[k][j]<lowcost[j]){ //遍历数组没访问过的才有更新的必要,更小的更新进去
                lowcost[j] = G.Edge[k][j];
            }
        }
    }
}

17、Kruskal算法

/*
 * Kruskal算法
 * 构造最小生成数的Kruskal算法
 * */
//思路:(假设n个顶点)找出n-1条边,递增的输出最小的边,并且判断符合条件不(不成环就是符合条件)
typedef struct {
    int s;  //开始的结点
    int e;  //结束的结点
    int weight; //权值
}edge;
int Find(int S[], int x){
    while (S[x] >= 0){
        x = S[x];
    }
    return x;
}   //双亲表示法里找根
void Kruska(MGraph G){
    edge e[G.arcnum];
    int k = 0;
    for(int i=0; i<G.vexnum; i++){
        for(int j=i+1; j<G.vexnum; j++){
            e[k].s = i;
            e[k].e = j;
            e[k].weight = G.Edge[i][j];
            k++;
        }
    }
    edge temp;
    int i, j;       //直接插入排序,给新结构体排序
    for(int i=1; i<G.arcnum; i++){
        if(e[i].weight < e[i-1].weight){
            temp = e[i];
            for(int j=i-1; temp.weight<e[j].weight; j--){
                e[j+1] = e[j];
            }
            e[j+1] = temp;
        }
    }
    int S[G.vexnum];
    int count = 0;
    int start, end;
    for(int i=0; i<G.vexnum; i++){     //初始化
        S[i] = -1;
    }
    for(int i=0; i<G.arcnum; i++){
        start = Find(S, e[i].s);
        end = Find(S, e[i].e);
        if(start != end){
            S[start] = end;
            printf("%d", e[i], e[i].e);
            count++;
            if(count == G.vexnum-1){
                break;
            }
        }
    }
}

18、Dijkstra算法

/*
 * Dijkstra算法
 * 与上个单源最短路径不同,Dijkstra是带权值的
 * */
void Dijkstra(MGraph G, int v){ //源点v
    int visited[G.vexnum]; //记录最终最短路径的终点,对应位置 置1
    int dist[G.vexnum];      //记录最短路径的权值
    int path[G.vexnum];     //记录到达最短路径过程中,到顶点的前驱一个结点
    for(int i=0; i<G.vexnum; i++){
        visited[i] = 0;     //先初始化遍历数组
    }
    for(int i=0; i<G.vexnum; i++){
        dist[i] = G.Edge[v][i]; //更新成源点所在的这一行
        path[i] = v;    //更新成 都是从源点到的
    }
    visited[v] = 1; //找到了最短路径置1
    int min, k; //记录dish数组的最小值,k记录对应的顶点
    for(int i=0; i<G.vexnum-1; i++){
        min = INT_MAX;  //初始化
        for(int j=0; j<G.vexnum; j++){
            if(visited[j] == 0 && dist[j]<min){
                min = dist[j];
                k = j;
            }
        }
        visited[k] = 1;
        for(int j=0; j<G.vexnum; j++){  //新加入了最短的顶点就要更新dish和path数组了
            if(visited[j] == 0 && dist[k]+G.Edge[k][j] < dist[j]){
                dist[j] = dist[k] + G.Edge[k][j];
                path[j] = k;
            }
        }
    }
}

19、Floyd算法

/*
 * Floyd算法
 * 各顶点之间的最短路径
 * */
void Floyd(MGraph G){
    int i, j, k;
    int A[MaxSize][MaxSize];    //记录最短路径权值的矩阵
    int path[MaxSize][MaxSize]; //记录到顶点路径上的顶点的一个前驱
    for(i=0; i<G.vexnum; i++){  //初始化两个矩阵
        for(j=0; i<G.vexnum; j++){
            A[i][j] = G.Edge[i][j];
            path[i][j] == -1;
        }
    }
    for(k=0; k<G.vexnum; k++){
        //遍历二维数组
        for(i=0; i<G.vexnum; i++){
            for(j=0; i<G.vexnum; j++){
                if(A[i][k] + A[k][j] < A[i][j] ){   //【通过中间一个顶点】比【原来直接到达】的还要短,更新
                    A[i][j] = A[i][k]+A[k][j];  //最终得到矩阵A就是各顶点直接最短路径得权值
                    path[i][j] = k;
                }
            }
        }
    }
}
void PrintPath(int u, int v, int A[][MaxSize], int path[][MaxSize]){
    if(A[u][v] == INT_MAX){
        printf("顶点%d到顶点%d没有路径", u,v);
    }
    else if (path[u][v] == -1){
        printf("%d-%d", u,v);
    }
    else{
        int mid = path[u][v];
        PrintPath(u, mid, A, path);
        PrintPath(mid, v, A, path);
    }
}

【串】

1、串的结构体定义及简单模式匹配算法

//串的结构体定义及简单模式匹配算法
typedef struct{
    char ch[MaxSize];
    int length;
}SString;

2、简单模式匹配

//简单模式匹配
int Index(SString S, SString T){
    int i=1, j=1, k=i;  //i是主串的位置,j是模式串的位置,k记录匹配失败后主串回溯的位置
    while (i<=S.length && j<=T.length){
        if(S.ch[i] == T.ch[j]){
            i++;
            j++;
        }
        else{
            i=k+1;
            j=1;
            k=i;
        }
    }
    if(j>T.length){ //匹配成功
        return k;
    }else{      //匹配失败
        return 0;
    }
}

3*、KMP算法

/*
 * 串的KMP算法
 * */

//求next数组
void get_next(SString T, int next[]){
    int i=1, j=0;
    next[1] = 0;
    while(i < T.length){
        if(j==0 || T.ch[i]==T.ch[j]){
            i++;
            j++;
            next[i] = j;
        }else{
            j = next[j];
        }
    }
}

//KMP算法
int KMP(SString S, SString T, int next[]){
    int i=1, j=1;
    while (i<=S.length && j<=T.length){
        if(j==0 || S.ch[i] == T.ch[j]){
            i++;
            j++;
        }else{
            j=next[j];  //与简单模式匹配的回溯不一样
        }
    }
    if(j>T.length){
        return i-T.length;  //找到了
    }else{
        return 0;   //没找到
    }
}

【注】手算求next数组很重要!!!


   考研加油!!此战成硕!!!φ(* ̄0 ̄)


  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值