C语言数据结构与算法

这篇具有很好参考价值的文章主要介绍了C语言数据结构与算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

排序


  • 冒泡排序
思想:两数比较,将最大或者最小的元素,放到最后
int i , j , temp		//定义 外层循环变量 i 内层循环变量 j 临时变量 temp

for( i = 0 ; i< len - 1 ; i++){		//外层 控制 比较多少趟
	
    for( j = 0 ; j < len - i -1 ; j++){		//内层 控制 元素 一趟比较的次数
        
        if( arr[j] > att[j+1]){		//从小到大排序
            
            temp = arr[j];
            
            arr[j] = arr[j+1];
            
            arr[j+1] = temp;
        }
        
	}
    
}
  • 例题
题目:定义一个数组,长度 10 , 子函数 键盘输入元素,子函数遍历数组,子函数进行冒泡排序,最终将排序好的元素,打印到文件中
  • 顺序表下的 冒泡排序
typedef int keyType				//关键字
typedef struct{
    
    ketType key;

}RecType;					   //单个结点的数据类型

typedef struct{
    
    RecType r[10000];
    int len;				   //顺序表长度
    
}SqList;					  //顺序表的数据类型

int i , j , temp;
SqList *q;

for(i = 0;  i < q->len-1 ; i++){
    
    
    for(j = 0 ; j < q->len-i-1 ; j++){

        if(q->r[j].key > q->r[j + 1].key){
            
            temp = q->r[j].key;
            
            q->r[j].key = q->r[j + 1].key;
            
            q->r[j + 1].key = temp;
            
        }
        
    }
    
}

注意:冒泡排序 稳定,最多执行n(n-1)/2次



选择排序


选择排序,找最大或者最小的元素 往前放,分成 已排序和待排序序列
void selectSort(int arr[] , int len){
    
    int i , j , temp , min;
    
    for(i = 0 ;i < len ; i++){
        
        min = i;				//min 标记 未排序第一个元素的位置
        
        for(j = i + 1 ; j < len ; j++){		//找比 min 位置还小的元素
            
            if(arr[j] < arr[min]){
                min = j;
            }
            
        }
        
        if(min != i){
            temp = arr[min];
            
            arr[min] = arr[i];
            
            arr[i] = temp;
            
        }
        
    }
    
}
题目:定义一个数组,键盘输入10个数据,利用选择冒泡排序从小到大输出在控制台上,并写入文件,另子函数读取文件,利用选择排序从大到小输出在控制台上。

选择排序不稳定,平均比较次数n(n-1)/2


直接插入排序


从后向前进行 查找 确定位置,并插入元素
void insertSort(int arr[] , int  len){
	
	int  i , j , temp;
	
	for(i = 1; i < len; i++){		//从 1 开始,0 位置当成有序
        
        temp = arr[i];				//temp 记录未排序第一个
        
        for(j = i - 1; j >= 0 && arr[j] > temp; j--){
            
            arr[j+1] = arr[j];		//元素后移,腾位置给插入元素

		} 
        arr[j+1] = temp;			//插入 比较的 后一位
        
	}
    
}
题目:选择功能,1、选择排序(从小到大) 2、直接插入排序(从大到小) 3、冒泡排序(从小到大)
选择分支结构,定义数组,键盘输入值,实现以上 三个子函数的功能

直接插入排序,是在有序基础上,速度最快且稳定的排序方法。


希尔排序


按照 步长 进行分组,然后每一组的 第 几个 元素 进行排序

增量 及 步长
//dk 是增量 步长

void shellInsert(int arr[] , int len ,int dk){
    
    int i , j , temp;		//i 循环变量 j 前边组的位置 temp 临时变量
    
    for(i = dk; i < len; i++){
        
        //判断每组的 第几个元素大小
        if(arr[i] < arr[i-dk]){		//后编组 小于 前边组
            
            temp = arr[i];
            
            j = i - dk;
            
            for(; j >= 0 && temp <arr[j]; j = j - dk){
                //前边的值 给到 后边
                arr[j + dk] = arr[j];
			}  
            arr[j + dk] = temp;
        }
        
    }
    
}

希尔排序是 不稳定的


快速排序


基准值,i 从前向后找,比基准值大的元素 j 从后向前找,找比基准值小的元素,最后,结束条件 i = j 此时将 基准值放到这个位置
int getPivot(int arr[] , int low , int high){
    
    int pivot = arr[low];
    
    while(low < high){			//i < j 的时候
        
        // j 从后向前找,比基准值 小的元素
        while(low < high && arr[high] >= pivot){
            high--;
        }
        
        //找到了 比基准值小的元素	将元素 给到 i 位置
        arr[low] = arr[high];
        
        //i 从前向后找,比基准值 大的元素
        while(low < high && arr[low] <= pivot){
            low++;
        }
        
        //找到了 比基准值大的元素	将元素 给到 j 位置
        arr[high] = arr[low];
        
    }
    
  	//结束条件 i = j 找到了基准值的位置
    arr[low] = pivot;
    
    //返回基准值
    return low;
    
}


void quickSort(int arr[] , int low ,int high){
    
    if(low < high){
        
        //基准值位置的变量
        int pivot = getPivot(arr , low , high);
        
        //递归 对 基准值位置 左边进行 快速排序
        quickSort(arr , low , pivot-1);
        
        //递归 对 基准值位置 右边进行 快速排序
        quickSort(arr , pivot+1 , high);
    }
}

查找


  • 顺序查找
遍历数组,拿元素依次比较,相同返回 下标,找不到,返回 -1
//arr 数组 len 数组长度 value 要查找的值
void linearSearch(int arr[] , int len , int value){
    int i;
    for(i = 0 ; i < len ; i++){
        
        if(arr[i] == value){
            return i;
        }
        
    }
    
    return -1;		//找不到
}
  • 二分查找(非递归)
//arr 数组,low 左边值,high 右边值,key 要查找的关键字
int binarySearch(int arr[] , int low , int high , int key){
    
    int mid;			//中间值
    
    while(low <= high){
        
        mid = (low +high)/2;		//找中间位置
        
        if(key == arr[mid]){
            return mid;				//返回下标
        }else if(key > arr[mid]){
            low = mid + 1;
        }else{
            high = mid - 1;
        }
        
    }
    //没有找到关键字
    return -1;
}
  • 二分查找(递归)
int binarySearch(int arr[] , int low , int high , int key){
   
    int mid;			//中间下标
    
    if(low <= high){
        
        mid = (low + high)/2;
        
        if(key == arr[mid]){
            return mid;		//递归出口
        }else if(key > arr[mid]){
            return binarySearch(arr , mid+1 , high , key);
        }else{
            return binarySearch(arr , low , mid-1 , key);
        }
        
    }
    return -1;
}
题目:主函数定义 数组 长度为5,键盘输入 5 个元素,子函数一,实现二分查找递归式子,子函数儿,实现二分查找,非递归方式。并输出

线性表


1、顺序表(数组)
	//顺序表 一般 占用一片连续的存储空间
2、链表
	//链表,结点在内存中随机分配,指针链接起来即可
数组 链表
查询
增删
索引,移动大量元素 只有指针,不需要移动元素

遇到数组是,注意:下标从0开始还是从1开始

数组名 是 首元素地址,也是指针

bit 位 	0100 0110
byte 字节

1 byte = 8 bit

int arr[] = {1 , 2 , 3 , 4 ,5};

int *p = arr;

//数组的首元素地址
arr == &arr[0] == p

顺序表(数组)


  • 输入元素
void input(int arr[] , int len){
    //循环变量
    int i;
    
    int *p = arr;
    
    for(i = 0;i < len; i++){
        printf("请输入%d个元素",i+1);
        scanf("%d",&arr[i]);
        //scanf("%d",arr + i);
        //scanf("%d",p + i);
    }
}
  • 输出元素(遍历数组)
void output(int arr[] , int len){
    //定义循环变量
    int i;
    
    int *p = arr;
    
    for(i = 0 ; i < len ;i++){
        printf("%d ",arr[i]);
        // printf("%d ",*(arr + i));
        // printf("%d ",*(p + i));
    }
}
  • 查询元素
//根据元素的值 去找 下标
int searchvalue(int arr[] , int len ,int value){
    
    int i;
    
    for(i = 0;i < len; i++){
        if(value == arr[i]){
            return i;
		}
    }
    return -1;
}
//根据下标 查找元素
int searchkey(int arr[] , int len ,int key){
    
    //判断 位置 是否在数组范围之内
    if(key < 0 || key > len -1){
        printf("位置不对!");
        return;						//exit(0)	j结束程序
    }
    
    printf("查找的元素是:%d\n",arr[key]);
}
  • 动态生成 数组
int *getArray(int num){					//返回一个指针变量
    	
    int i,*p;				//p 指向 动态数组首地址
    
    p = (int *)malloc(sizeof(int)*num);
    
    if(!p){
        printf("空间申请失败!");
        return;
    }
    
    return p;
}
  • 修改元素
//根据元素值修改
void updateValue(int arr[] , int len){
    
    //修改前		  修改后
    int firstValue , lastValue , i;
    
    printf("请输入您要修改的元素:\n");
    scanf("%d",&firstValue);
          
    printf("请输入您要修改后的元素:\n");
    scanf("%d",&lastValue);
    
    for(i = 0; i < len; i++){
        if(arr[i] == firstValue){
            arr[i] = lastValue;
        }
    }
}
//根据位置修改  
void updateKey(int arr[] , int len){
    
    int key , value;
    
    printf("请输入你要修改的位置:\n");
    scanf("%d",&key);
    
    printf("请输入你要修改后的值:\n");
    scanf("%d",&value);
    
    if(key < 0 || key > len - 1 ){
        printf("位置不对!");
        return;
    }
    
    arr[key] = value;
}
  • 插入元素
void insert(int arr[] , int *len , int index , int value){
    
    int i,temp;
    
    if(index < 0 || index > *len -1 ){
        return;
    }
    
    //元素后移
    for(i = *len - 1; i >= index; i--){
        arr[i+1] = arr[i];
    }
    
    //插入元素
    arr[index] = value;
    
    //长度变化
    (*len)++;
}
  • 删除元素

    void delete(int arr[] , int *len , int index){
        
    	int i;
    
    	if(index < 0 || index > *len - 1){
        	return;
    	}
    
    	//元素前移
    	for(i=index; i<len-1; i++){
       		arr[i] = arr[i+1];
    	}
    
    	//元素删除 长度减少
    	(*len)--;
    }    
    
  • 字符数组

//1、大括号初始化字符数组时,需要预留一个空间 做 结束标志 \0,否则乱码
char a[5] = {'a' , 'b' , 'c' , 'd' , 'c'}			//乱码

//2、双引号初始化时,自带结束标志
char str[] = "hello";

strlen 求字符串长度时,不带\0

sizeof 求字符串长度时,带\0

  • 字符数组两种 键盘输入方式
1gets(字符串);

2scanf("%s",字符串);
//可以校验 是否越界,stdin 是标准输入流(键盘输入)
fgets(a , sizeof(a) , stdin);

链表


  • 结点
typedef struct Node{
	int data;				//数据域
    struct Node *next		 //指针域(存放下一个结点的位置)
}Node;
  • 结点类型
1、头结点				//只有指针域,数据域不存放数据,一般用 head 表示
2、首元结点			    //第一个存放 数据的 结点

有头结点:方便使用头插法 插入元素

  • 链表的初始化(只生成头结点)
typedef struct Node{
    int data;						//数据域
    struct Node *next;				 //指针域
}Node,*LinkeList;
//Node struct Node 的别名 LinkList struct Node *
//int int *

LinkList create(){
    
    //动态申请空间	(head指针指向这个空间)
    LinkList head = (Node *)malloc(sizeof(Node));
    
    //判断空间是否开辟成功
    if(!head){
        peinrf("空间开辟失败!");
        return 0;
    }
    
    //头结点,指针域赋值为 NULL
    head->next = NULL;
    
    //返回头结点 指针
    return head;
}
  • 链表长度获取
//实际传的是 头结点地址,但是代表的是一个 链表
int getsize(LinKist head){
    
    //头结点 为 空
    if(head == NULL){
        return NULL;
    }
    
    //p 指向首元结点
    Node *p = head->next;
    //LinkList q = head->next;
    
    //计数器
    int count = 0;
    
    while(p != NULL){
        count ++;
       	p = p->next;				//指针后移
    }
    
    //返回链表长度
    return count;
    
}
  • 链表的遍历(数据域)
void foreach(LinkList head){
    
    if(head == NULL){
        return;
    }
    
    LinkList p = head->next;
    
    while(p != NULL){
        
        printf("%d ",p->data);
        p = p->next;				//指针后移
    }
}
  • 链表的清空
void clear(LinkList head){
    
    if(!head){
        return;
    }
    
    Node *p = head->next;
    
    while(p != NULL){
       
        //q 存放的是 p 下一个结点地址
        LinkList q = p->next;
        
        //释放 p
        free(p);
        
        p = q;
    }
    
    head->next = NULL;
}
  • 链表的销毁
void destroy(LinkList head){
    
    if(head == NULL){
        return;
    }
    
    clear(head);
    
    free(head);
    
    head = NULL;			//指针变量设置为 空
    
}
  • 链表的插入(尾插法)
void insert(LinkList head){
    
    if(!head){
        return;
    }
    
    int num,i;
    
    printf("请输入你要插入的结点数量:\n");
    scanf("%d",&num);
    
    Node *q = head;
    
    for(i = 0 ;i < num ;i++){
        LinkList p = (Node *)malloc(sizeof(Node));
        
        printf("请输入数据域的值:");
        scanf("%d",&p->data);
        
        q->next = p;
        
        q = q->next;
    }
    
    q->next = NULL;
}

尾插法生成 链表

  • 链表的插入(头插法)
void insert(LinkList head){
    
    if(head == NULL){
        return;
    }
    
    int num,i;
    
    printf("请输入你要插入的结点数量:\n");
    scanf("%d",&num);
    
    
    for(i = 0;i < num; i++){
        
        Node * p = (Node *)malloc(sizeof(Node));
        
        printf("请输入数据域的值:");
        scanf("%d",&p->data);
        
        p->next = NULL;
        
        //头插法
        p->next = head->next;
        
        head->next = p;
    }
    
}

头插法,经常用头指针进行元素的插入

  • 链表的删除
//根据元素的值去删除结点
void delete(LinkList head , int value){
    
    if(head == NULL){
        return;
    }
    
    Node *p = head;					//p 指针指向头结点 
    
    Node *q;
    
    while(p->next != NULL){
        
        if(p->next->data == value){
            q = p->next;
            
            p->next = q->next;
            
            free(q);
        }else{
             p = p->next;
        }
        
    }
   
}

删除一个结点,必须知道这个结点 的 直接前驱


栈(stack)


  • 栈的特点:先进后出 后进先出
1、栈分成了 栈顶 和 栈底 (在栈顶进行元素的插入和删除)
2、入栈 (push)		出栈 (pop)
  • 栈的顺序存储 (使用 数组)
数组的首地址做栈顶还是栈底比较好?

答:做栈底 比较好,因为 栈顶要进行元素的插入和删除操作,所以数组尾部更适合比较频繁插入和删除

  • 栈的 结构体
typedef struct Stack{
    
    int data[MAX];				//数据域
    int size;					//栈的大小(栈中元素个数)
    
}Stack , *SeqStack;
  • 栈的初始化
SeqStack create(){
    
    //创建栈,相当于动态开辟一个数组出来
    SeqStack p = (Stack *)malloc(sizeof(Stack));
    
    if(p == NULL){
        return NULL;
    }
    
    //初始化 栈的大小
    p->size = 0;
    
   	//初始化 栈中的元素
    for(int i = 0; i < MAX; i++){
        p->data[i] = 0;
    }
    
    return p;
}
  • 入栈
void push(SeqStack stack , int data){
    
    if(stack == NULL){
        return;
    }
    
    //入栈 需要判断 栈是否已满
    if(stack->size == MAX){
        printf("栈已满!\n");
        return;
    }
    
    //stack->size 栈的大小就是 我们插入元素的下一个位置
    stack->data[stack->size] = data;
    
    //改变栈的大小
    stack->size++;
}

入栈 需要注意 栈是 不存在 栈是否已满

  • 出栈
void pop(SeqStack stack){
    if(stack == NULL){
        return;
	}
    
    if(stack->size == 0){
        printf("栈已空!\n");
        return;
    }
    
    //删除一个元素
    stack->data[stack->size - 1] = 0;
    
    //改变栈的大小
    stack->size --;
    
}

出栈 需要注意 栈是否 不存在,和 栈是否已空

  • 遍历栈中元素
void foreachSack(SeqStack stack){
	
    if(stack == NULL){
        return;
    }
    
    for(int i = 0; i < stack->size; i++){
        printf("%d ",stack->data[i]);
    }
    
}

栈的链式存储(链表)


链表的头结点端做栈顶还是栈底?

答:栈顶 因为 链表的头结点端 可以 更方便的进行元素的 插入 (头插法) 和 删除 (头结点做直接前驱)

  • 栈链式存储 结构体
//结点结构体
typedef struct Node{
    int data;
    struct Node *next;
}Node;

//栈的结构体
typedef struct Stack{
    
    struct Node header;					 //头结点
    int size;							//栈的大小及元素个数
    
}Stack , *LinkList;
  • 初始化栈
LinkStack init_LinkStack(){
    
    //myStack 是指针 指向我们申请的空间
    LinkStack myStack = (Stack *)malloc(sizeof(Stack));
    
    if(myStack == NULL){
        return NULL;
    } 
    
    //初始化长度 为 0
    myStack->size = 0;
    
    //相当于 初始化链表的头结点 指针域
    myStack->header.next = NULL;
}

什么时候使用-> 什么时候使用 . ?

-> 在指针的时候

. 在变量名的时候

  • 注意事项
struct Node * list				list->data	list->next
struct Node a;					a.data		a.next
  • 入栈
void push(LinkStack srack , int data){
    
    if(stack == NULL){
        return;
    }
    
    //创建一个结点
    Node * p = (Node *)malloc(sizeof(Node));
    
    //给结点数据域 赋值
    p->data = data;
    
    //头插法 线连后边
    p->next = stack->header.next;
    
    stack->header.next = p;
    
    //改变栈中元素的个数
    stack->size++;
}
  • 出栈
void pop(LinkStack stack){
    
    if(stack == NULL){
        return;
    }
    
    if(stack->size == 0){
        printf("栈中无元素!");
        return;
    }
    
    //定义一个结点类型指针 用作 删除 结点
    Node *p = stack->header.next;
    
    //头结点 连接 到 删除结点后边
    stack->header.next = p->next;
    
    //释放结点
    free(p);
    
    //更新栈大小
    stack->size --;
    
}

队列


  • 队列:先进先出 后进后出
1、只允许从 一端添加元素 从另一端删除元素
2、对头 出元素	队尾 进元素
3、push			pop
  • 队列的顺序存储
数组的首地址 做队头好,还是队尾好

答:都一样,做对头 和 队尾差别不大,因为 都需要移动大量元素

  • 队列的结构体
typedef struct Queue{
    int data[MAX];				//顺序存储 数组模拟的队列
    int size;					//队列的大小(队列中元素个数)
}Queue , *SeQueue;
  • 队列的初始化(数组头做对头,数组尾做队尾)
SeQueue init(){
    
    //申请队列空间,即数组空间
    SeQueue  p = (Queue *)malloc(sizeof(Queue));
    
    if(p == NULL){
        return NULL;
    }
    
    //初始化 队列大小
    p->zieof = 0;
    
    int i;
    
    for(i = 0; i < MAX; i++){
        p->data[i] = 0;
    }
    
    return p;
    
}
  • 队列的入队
void push(SeQueue p , int value){
    
    if(p == NULL){			//队列不存在
        return;
    }
    
    if(p->size == MAX){
        printf("队列元素已满!\n");
        return;
    }
    
    //进队
    p->data[p->size] = value;

    //改变队列元素个数
    p->size ++;
}
  • 出队
void push(SeQueue p){
    
    if(p == NULL){			//队列不存在
        return;
    }
    
    if(p->size == 0){
        printf("队列元素已空!\n");
        return;
    }
    	
    //出队		删除数组首元素
    int i;
    
    for(i = 0; i < p->size-1 ; i++){
        p->data[i] = p->data[i+1];
    }

    //改变队列元素个数
    p->size --;
}

要记得,看到循环里边有 i + 1 的时候,要改变 最终值

  • 队列的链式存储
链表的 头结点端 做对头 还是 队尾?

答:做对头好,因为 头结点端进行删除方便,尾端 插入元素方便(尾插法)

  • 队列链式结构体
struct Node
{
	int data;			//数据域
	struct Node* next;  //指针域
};

//队列的结构体
typedef struct LQueue
{
	struct Node header; //头结点
	int size;  //队列大小
	struct Node* pTail; //尾结点指针
}LQueue, *LinkQueue;
  • 循环队列
//队满
front = (rear + 1)%n;						//front 头,rear 尾

//队空
front = rear;							   //头指针 指向 尾指针 为空

栈的应用


  • 前 中 后 缀表达式如何计算?
首先运算优先级最高的,然后 将 运算符 放两个整体 之前 就是 前缀,放两个整体之后,就是 后缀!


  • 零个或者多个任意字符组成的有限序列是我们的字符串

  • 串 是 内容受限的线性表

  • 子串:一个串中 任意一个连续字符组成的子序列(含空串

"*jkj12k0"		//它的字串有那些
    
	// 单个字符		6
	// 俩个字符		7
	// 三个字符		6
	// 四个字符		5
	// 五个字符		4
	// 六个字符		3
	// 七个字符		2	
	// 八个字符		1
	// 空串  		  1
    
    
35
  • 真子串(不包含自身的子串)
  • 主串:包含子串的串,称之为 主串
  • 字符位置:字符在数组中的位置
  • 子串位置:子串第一个字符 在 主串中的位置
  • 空格串:一个空格或者多个空格组成的串,称之为空格串
  • 空串:零个没有任意字符的串就是我们的空串
" "			//空格串
""    		//空串
  • 串相等:当两个串长度相等,并且每个位置对应字符完全一样的时候,两个串才相等。

串的基本操作


1. 赋值	StrAssign(s,t)    	 	将串t的值赋值给串s
2. 求串长  StrLength(s)    		返回字符串s的长度
3. 串比较  StrCompare(s,t) 		比较两个串,返回-101,分别代表s<t、s=t和s>t
4. 连接 Concat(s,t)  				将串t连接在串s的尾部,形成新的字符串
5. 求子串 SubString(s,start,len)   返回串s从start开始、长度为len的字符序列
6. 串拷贝 StrCopy(t,s) 		   将串s拷贝到串t上
7. 串判空 StrEmpty(s)         		判断串s是否为空串
8. 清空串ClearString(s)      	    清空字符串s
9. 子串位置  Index(S,T,pos)        字符串S中返回T所出现的位置
10. 串替换  Replace(S,T)      	    串S替换为T
  • 串的顺序存储
    • 结构体
typedef struct SString{
    char ch[MAX + 1];				//字符数组空间
    int lingth;					    //串的长度
};
  • BF算法
//str 是主串 subStr是模式匹配串
int BF(char str[] , char subStr[]){
    int i , j;			//定义 i 主串下标 j 模式串下标
    
    i = 0;
    j = 0;
    
    while(i < strlen(str) && j < strlen(subStr)){
		
        //如果对应位置相等,则下标后移比较下一个元素
        if(str[i] == subStr[j]){
            i++;`标进行回溯
            j = 0;			    //子串下标回溯
        }
    }
    
    if(j == strlen(subStr)){		//子串下标 和 子串长度相等
        //返回 子串位置
        return i - strlen(subStr) + 1;
    }
    
    return -1;				//没有找到子串下标
}

矩阵


  • 把矩阵当作 二维数组,只不过下标都是从 1 开始
  • 二维数组 以行为主序存储,二维数组 以列为主序存储
以行存储,先去计算前 i - 1 行有多少元素
以列存储,先去计算前 j - 1 列有多少元素

前提:数组下标 从 1 开始

  • 对称矩阵:以主对角线为对称轴,各元素对应相等的矩阵

  • 对角矩阵:除主对角线以外,全是 0 的矩阵

  • 稀疏矩阵:非零元素个数,远远少于 零元素个数,且零元素分布没有规律

  • 三元组(行,列,值)

(4 , 5 , 8)					//第四行,第五列,值为 8


  • 树是非线性结构
  • 根结点:没有父结点的结点,称之为 根结点
  • 叶子结点:没有子节点的结点
  • 结点的层次:有几层
  • 结点的祖先:从根结点到该节点的 所有结点
  • 结点的度:该节点有几个孩子,就是几度
  • 树的度:树中,结点的度 最大的 称之为 树的度
  • 树的深度/高度:从根节点开始,长度最长的那条路径,有几个结点就是 多高

  • 树的性质
1、树中的结点树,等于所有结点的度数加1
2、度为 m 的树中,第i层上至多有 m 的(i - 1)次方个结点(i >= 13、高度为 h 的 m 叉树中,至多有 (m 的 h 次方) / (m - 1)个结点 (二叉树再减一)
  • 二叉树:有零个到两个结点的树,称之为 二叉树

  • 二叉树的性质

    • 性质一:在二叉树中 第 i 层上最多有 **2 i-1**个结点(i > 0)
    • 性质二:深度为 k 的二叉树,最多有 2k-1个结点(k > 0)
    • 性质三:树上的结点树为 n ,则边数一定为n - 1
    • 性质四:任意一颗二叉树中,度为 0 的结点个数 = 度为 2 的节点个数 + 1
    • 性质五:具有 n 个节点的 完全二叉树 的深度为**⌊log2n⌋**+ 1 (向下取整)
  • 大顶堆 , 小顶堆

//大顶堆 每个根节点(所有的根节点)都大于它的子节点
ki >= k2i
ki >= k2i+1
    
//大顶堆 每个根节点(所有的根节点)都小于它的子节点
ki <= k2i
ki <= k2i+1    

二叉树链式存储


  • 结点的定于
typedef struct TreeNode{
    char ch;					//数据域
    struct TreeNode *lchild;		 //左孩子指针
    struct TreeNode *rchild;		 //右孩子指针
};

当没有 左右孩子结点的时候,左后孩子指针 为 NULL 值

  • 遍历方式

    • 先序遍历:根左右
    • 中序遍历:左根右
    • 后续遍历:左右根
  • 先序遍历

void perOrdef(Node * root){				//root根节点的意思
    
    	//递归 结束的条件,也就是当一个结点 某个指针域为 NULL
    	if(root == NULL){
			return;
        }
    
    	printf("%c ",root->ch);
    
    	//递归调用左子树
    	perOrder(root->lchid);
    
    	//递归调用右子树
    	perOrder(root->rchild);
}
  • 中序遍历
void inOrdef(Node * root){				//root根节点的意思
    
    	//递归 结束的条件,也就是当一个结点 某个指针域为 NULL
    	if(root == NULL){
			return;
        }   
    
    	//递归调用左子树
    	perOrder(root->lchid);
    
    	printf("%c ",root->ch);
    
    	//递归调用右子树
    	perOrder(root->rchild);
}
  • 后续遍历
void lastOrdef(Node * root){				//root根节点的意思
    
    	//递归 结束的条件,也就是当一个结点 某个指针域为 NULL
    	if(root == NULL){
			return;
        }
    
    	//递归调用左子树
    	perOrder(root->lchid);
    
    	//递归调用右子树
    	perOrder(root->rchild);
    
    	printf("%c ",root->ch);
}

  • 根据遍历的序列 确定 二叉树 的形状
//1、先序和 中序 可以确定一棵二叉树
	答:根据先序确定 根节点的位置,然很去中序中找到该节点左边元素都是左子树,右边元素都是右子树,一次重复上述过程,即可根据序列确定 二叉树形状。
	(从前找根节点)
//2、后续和 中序 可以确定一颗二叉树
	答:根据先序确定 根节点的位置,然很去中序中找到该节点左边元素都是左子树,右边元素都是右子树,一次重复上述过程,即可根据序列确定 二叉树形状。
	(从后找根节点)
//3、先序和 后序 不能确定一颗二叉树
  • 非递归中序遍历
void inOrder(Node * root){
    
    //指针p 指向 根节点,并按照某种顺序游走于每一个结点
    Node *p = root;
    
    //建立栈,用来实现 递归
    LinkStack s = init_LinkStack();			//初始化栈,初始化一个头结点出来
    
    //p 不指向 NULL 并且 栈不空的时候
    while(p || ! isEmpty_LinkStack(s)){
        
        //根节点不空,进栈,让其指向左子树
        if(p){
            push_LinkStack(s , p);   
            
            p = p->lchild;				//指向左子树
        }else{
            
            //定义一个指针 指向 栈顶元素 (就是 子树的根节点)
            Node *top = top_LinkStack(s);
            
            //元素出栈
            pop_LinkStack(s);
            
            printf("%c ",top->data);
            
            p = top->rchild;
        }
    }
    
    //销毁栈
    destory_LinkStack(s);
}
  • 如果有 n 个结点,则有n + 1个空指针域

  • 线索 指向其前驱和后继的一个指针

  • 孩子兄弟表示法:左边指针是孩子,右边指针是兄弟


二叉排序树


  • 概念:是一颗空树,或者是满足以下性质的二叉树
    • 左子树非空,左边结点 小于 根节点
    • 右子树非空,右边节点 大于 根节点
    • 中序遍历,可以实现 二叉排序树从小到大排序
//定义节点 结构体
typedef struct TreeNode{
    int data;
    struct TreeNode *lChild;				//左孩子指针
    struct TreeNode *rChild;				//右孩子指针
}TreeNode;

TreeNode* searchTreeNode(TreeNode *root , int key){
    
    //递归结束条件 root 为 NULL 相当于没有找到节点
    if((root == NULL) || (key == root->data)){
        return root;
    }		
    else if(key < root->data){		//递归左子树查找
        
		return searchTreeNode(root->lChild , key);
        
    }
    else{						  //递归右子树查找
       
        return searchTreeNode(root->rChild , key);
        
    }
}

哈夫曼树(最小权值树)


  • 路径:两个节点之间的 线就是我们的路径
  • 节点的路径:从根节点到 此节点,之间有几根线,路径长度就是多少
  • 树的路径长度:所有节点路径长度 之和 称之为 树的路径长度
  • 权值:给这个节点赋一个数值,这个数值就是我们的权值
  • 节点带权路径长度:路径长度 * 该节点权值
  • 树的带权路径长度(WPL):所有叶子节点的带权路径长度之和
  • 重点:哈夫曼树构造
//哈夫曼二叉树构造
1、每次从 序列中找出 权值最小的两个节点(一般,从左至右,从小到大)
2、生成一个新的节点,将这个节点放入 序列中,删除刚才的两个最小节点,再重复一过程

邻接矩阵


  • 无向图对应的邻接矩阵是 对称矩阵
  • 矩阵的大小 只与顶点个数有关 是一个 N*N阶矩阵
  • 顶点的度:
    • 入度:邻接矩阵对应于 列上值的 总和
    • 出度:邻接矩阵对应于 行上值的 总和

邻接表


  • 防止浪费空间(稀疏矩阵),但是效率相对于 邻接矩阵 较低
  • 邻接表:顶点用一维数组存储,后面跟着单链表 存储 邻接点
  • 邻接表 中 邻接点的个数 和 图中这个顶点 的出度有关

带权图


  • 边 给一个值,称之为 边权,对应图 称之为 带权图,也称之为

深度优先遍历DFS


  • 不撞南墙不回头(遍历不唯一)

广度优先遍历算法BFS


  • 相当于我们二叉树的层次遍历(用到队列,先进先出)
  • 记得保持顺序

G = (V,E):G指的图 V指的顶点 E指的边

  • 强连同图:任意顶点都能到达其他顶点,称之为,强连通图

最小生成树


  • 生成树:顶点由边链接在一起,不存在回路的图

    • 生成树顶点个数 和 图顶点个数相同
    • 生成树(极小连通图)去掉一条边,则变成非连通图
    • 连通图有 n 个顶点 则其 生成树 有 n - 1 条边
    • 生成树 再加一条,必然形成回路

    含有 n 个顶点 n - 1 条边的图,不一定是生成树

  • 无向图的生成树

    • 深度优先生成树
    • 广度优先生成树
  • 最小生成树:边权值之和最小的树文章来源地址https://www.toymoban.com/news/detail-736380.html


普利姆算法(Prim)


  • 普利姆算法 和 有关
  • 每次去找和点 连接的最小的边(包括之前的点)

克鲁斯卡尔算法(Kruskal)


  • 克鲁斯卡尔 和 有关
  • 每次去找 边最小的 然后,两个顶点相连,依次构成最小生成树

最短路径


迪杰斯特拉(Dijkstra)
  • 两点间最短路径
终点
i i=1 i=2 i=3 i=4 i=5 i=6
v1
v2
v3
v4
v5
v6
vj
距离

弗洛伊德(Floyd)
  • 所有顶点间的最短路径
  • 两个矩阵,一个存储 权值,一个存储 路径
  • 把所有的顶点 依次加入矩阵中,找最小的

拓扑排序


AOV网
  • 顶点 表示 活动
  • 弧 表示活动之间的优先制约关系

  • 拓扑排序:前提有向 无环图
  • 拓扑排序:找(无前驱)入度为 0 的结点 删除,循环上述过程即可
  • 逆拓扑排序:找(无后继) 出度为 0 的结点 删除,循环上述过程即可
  • 判断 AOV 网中是否有 环

到了这里,关于C语言数据结构与算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】排序算法的稳定性分析

    💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤 📃 个人主页 :阿然成长日记 👈点击可跳转 📆 个人专栏: 🔹数据结构与算法🔹C语言进阶 🚩 不能则学,不知则问,耻于问人,决无长进 🍭 🍯 🍎 🍏 🍊 🍋 🍒 🍇 🍉 🍓 🍑 🍈 🍌 🍐 🍍 前言: 前面我们已经

    2024年02月08日
    浏览(21)
  • 内部排序算法比较-数据结构C语言课设

    名称: 内部排序算法比较 内容: 在教科书中,各种内部排序算法的时间复杂的分析结果只给出了算法执行时间的阶,或大概执行时间。试通过随机数据比较各种算法的比较次数和移动次数,以取得直观感受。 任务: (1)对以下7中常会用的内部排序算法进行比较

    2024年02月12日
    浏览(24)
  • 【数据结构】八大排序算法(内含思维导图和画图分析)

    作者主页: paper jie_博客 本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。 本文录入于《JAVA数据结构》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将javaSE基础知识一网打尽,希望可以帮到读者们哦。 其他专栏:

    2024年02月08日
    浏览(17)
  • 【数据结构】排序算法复杂度 及 稳定性分析 【图文详解】

    前面给大家讲述了各大排序算法的原理、思路以及实现步骤、代码码源,下面让我们来对比一下各大排序之间的算法复杂度以及稳定性分析优劣,加深我们对于各排序算法的理解,帮助我们以后能更快的在具体场景下选择出最适的排序算法。 【数据结构】冒泡排序 (码源实

    2024年02月05日
    浏览(17)
  • 第11章:C语言数据结构与算法初阶之排序

    排序是一种非常重要的算法。 排序 :所谓排序,就是使一串记录,按照其中的某个或某些的大小,递增或递减的排列起来的操作。 稳定性 :假定在待排序的记录序列中,存在多个具有相同的的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,

    2024年02月12日
    浏览(16)
  • 【数据结构与算法】JavaScript实现排序算法

    一、大O表示法 大O表示法: 在计算机中采用 粗略的度量 来描述计算机算法的 效率 ,这种方法被称为 “大O”表示法 在 数据项个数 发生改变时, 算法的效率 也会跟着改变。所以说算法A比算法B快两倍,这样的比较是 没有意义 的。 因此我们通常使用 算法的速度 随着 数据

    2024年02月02日
    浏览(17)
  • 【数据结构与算法】归并排序详解:归并排序算法,归并排序非递归实现

    归并排序是一种经典的排序算法,它使用了分治法的思想。下面是归并排序的算法思想: 递归地将数组划分成较小的子数组,直到每个子数组的长度为1或者0。 将相邻的子数组合并,形成更大的已排序的数组,直到最终得到一个完全排序的数组。 归并排序的过程可以分为三

    2024年01月22日
    浏览(23)
  • 【数据结构与算法】:非递归实现快速排序、归并排序

    🔥 个人主页 : Quitecoder 🔥 专栏 :数据结构与算法 上篇文章我们详细讲解了递归版本的快速排序,本篇我们来探究非递归实现快速排序和归并排序 快速排序的非递归实现主要依赖于栈(stack)来模拟递归过程中的函数调用栈。递归版本的快速排序通过递归调用自身来处理子

    2024年03月24日
    浏览(23)
  • 【数据结构与算法C++实现】3、排序算法

    原视频为左程云的B站教学 外层循环 :n个数需要冒n-1个泡上去,剩下的一个必然是最小的。所以外层循环执行n-1轮 内层循环 :比大小,第1个泡需要比n-1次,第2个泡,比较n-2次… 选择: 每次从待排序序列中选择 最小的一个 放在已排序序列的后一个位置 原理类似于对扑克牌

    2024年02月11日
    浏览(22)
  • 常见排序集锦-C语言实现数据结构

    目录 排序的概念 常见排序集锦      1.直接插入排序      2.希尔排序      3.选择排序      4.堆排序      5.冒泡排序      6.快速排序             hoare              挖坑法             前后指针法             非递归      7.归并排序             非递归 排序实现

    2024年02月12日
    浏览(20)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包