C语言 数据结构与算法

发布时间:2024年01月20日

排序算法

冒泡排序
  • 冒泡排序

    思想:两数比较 将最大 或 最小 的元素,放到最后。
    
    int i, j, temp;		// 定义 外层循环变量 i,
    
    // i 外层循环变量,j 内层循环变量 ,temp 临时变量
    
    for( i=0 ; i<len-1 ; i++ ){		// 外层 控制 元素 比较多少躺
        
        for( j=0 ; j<len-i-1 ; j++ ){	//内层 控制 元素 比较次数
            
            if( arr[j] > arr[j+1] ){	//从小到大
                
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
                
            }
            
        }
        
    }
    
  • 顺序表 下的 冒泡排序

    typedef int keyType;		//关键字
    
    typedef struct{
        KeyType 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;
            }
    		
        }
    	
    }
    

    选择排序 不稳定,平均比较次数 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;		// 插入 比较的后一位
    
        }
        
    }
    

    直接插入排序,是在有序基础上,速度最快且最稳定的排序方法,最多执行 n(n-1)/2 次。


希尔排序
  • 希尔排序

    希尔排序:又称为 "缩小增量排序"。
    思想:按照 步长 进行分组,然后每一组的 第 几个 元素 进行排序。
    增量 及 步长。
    
    // 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 && arr[j]>temp ; j-=dk ){
                    //前面的值 给到 后面
                    arr[j+dk] = arr[j];
                }
                arr[j+dk] = tmep;
                
            }
            
        }
        
    }
    

    希尔排序 是 不稳定的。


快速排序
  • 快速排序

    基准值,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++;
            }
            
           arr[high] = arr[low];
            
        }
        
        // 找到了 比基准值大的元素,将 元素 给到 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);
        }
    }
    
    int main(){
        int arr[]={4,2,7,5,9};
    }
    

查找算法

顺序查找
  • 顺序查找

    遍历数组,拿元素一次比较,相同 返回 下标,找不到 则 返回 -1。

    // arr数组, len 长度, value 待查找值
    int linearSearch(int arr[], int len, int vaule){
        
        for(int i = 0; i < len; i++){
    
            if( arr[i] == value ){
    
                return i;		//返回下标
                return i+1;		//返回 第几位
    
            }
    
        }
    
        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;			//key 大于中间值,头移动至中间值后一位
            } else {
                high = low - 1;			//key 小于中间值,尾移动至中间值前一位
            }
            
        }
        //没有找到待查找数据
        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);
            }else {
                return binarySearch(arr, low, mid-1);
            }
    
        }
    
        return -1;
    
    }
    

顺序表

  • 线性表

    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 searchVlue(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);		结束程序
        }
        
        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;
        
        printf("请输入你要修改的元素:");
        scanf("%d",&firstValue);
        
        printf("请输入你要修改后的元素:");
        scanf("%d",&lastValue);
        
        for(int i=0; i<len; i++){
            if(arr[i] == firstValue){
                arr[i] = lastValue;
            }
        }
        
    }
    
    //根据 元素位置 修改 元素值
    void uodateKey(int arr[], int len){
        
        int key, value;
        
        printf("请输入你要修改的位置:");
        scanf("%d",&key);
        
        printf("请输入你要修改后的值:");
        scanf("%d",&value);
        
        if( key<0 || key >len-1 ){
            printf("位置不对!");
            reutnr;
        }
        
        arr[key] = value;
        
    }
    
  • 插入元素

    void insert(int arr[], int *len, int index, int value){
        
        int i;
        
        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 arr[5] = {'h','e','l','l','o'};		//乱码
    
    // 2、使用双引初始化时,自带结束标志
    char str[] = "hello";
    

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

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

  • 字符数组 两种 键盘录入方式

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

链表
  • 结点

    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 *
    
    LinkList create(){
        
        //动态申请空间(head 指针指向这个空间)
        LinkList head = (Node *)malloc(sizeof(Node));
        
        //判断空间是否开辟成功
        if(!head){
            printf("空间申请失败!\n");
            return NULL;
        }
        
        //头节点,指针域值为 NULL
        head->next = NULL;
        
        //返回头结点 指针
        return head;
        
    }
    
  • 链表长度获取

    // 实际传的是 头结点的地址,但是代表的是 一个链表
    int getSize(LinkList head){
        
        //头结点 为空
        if(!head){
            return 0;
        }
        
        // p 指向首元结点
        Node *p = head->next;	// 等价于    LinkList q = head->next;
        
        //计数器
        int count = 0;
        
        while(p){
            count++;
            p = p->next;		//指针后移
        }
        
        //返回链表长度
        return count;
        
    }
    
  • 链表的遍历(数据域)

    void foreach(LinkList head){
        
        if(!head){
            return;
        }
        
        LinkList p = head->next;
        
        while(p){
            printf("%d ",p->data);
            p = p->next;
        }
        
    }
    
  • 链表的清空

    void clear(LinkList head){
        
        if(!head){
            return;
        }
        
        Node *p = head->next;
        
        while(p){
            
            // q 存放的是,p 下一个结点的地址
            LinkList q = p->next;
            
            // 释放 p
            free(p);
            
            p = q;
            
        }
        
        head->next = NULL;
        
    }
    
  • 链表的销毁

    void destory(LinkList head){
        
        if(!head){
            return;
        }
        
        clear(head);
        
        free(head);			// 释放头结点
        
        head = NULL;		// 指针变量 设置为 空
        
    }
    
  • 链表的插入(尾插法)

    void insert(LinkList head){
        
        if(!head){
            return;
        }
        
        int num;
        
        printf("请输入要插入的结点数量:");
        scanf("%d",&num);
        
        Node *q = head;
        
        for(int 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){
            return;
        }
        
        int num;
        
        printf("请输入要插入的结点数量:");
        scanf("%d",&num);
        
        for(int 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){
            return;
        }
        
        Node *p = head;			// p 指针 指向 头结点的下一个
        
        Node *q;
        
        while( p->next ){
            
            if( p->next->data == value ){		// 若满足条件,则 p 就是待删除元素结点的直接前驱
                
                q = p->next;		// 令 q 指向 待删除结点
                
                p->next = q->next;		// 直接前驱结点 指向 待删除结点的 后一位结点
                
                free(q);		// 此时 释放结点
                
            } else{
                
                p = p->next;
                
            }
            
        }
        
    }
    

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


栈(stack)

  • 栈的特点:先进后出 / 后进先出

    1、栈 分成了 "栈顶""栈底" (在栈顶进行元素的插入和删除)
    2、入栈(push)		出栈(pop)
    
  • 栈的顺序存储(使用 数组)

    数组的首地址 做 "栈顶" 还是 "栈底" 比较好?
        
    答:做  "栈底"  比较好。
    

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

    • 栈 的 结构体

      typedef struct Stack{
          
          int data[MXA];		//数据域 (MAX 为 宏定义值)
          int size;			//栈的大小 (指 栈中的元素个数)
          
      }Stack, * SeqStack;
      
    • 栈 的 初始化

      SeqStack create(){
          
          //创建栈,相当于 动态开辟一个数组出来
          SeqStack p = (Stack *)malloc(sizeof(Stack));
          
          if(!p){
              printf("空间开辟失败!");
              return;
          }
          
          //初始化 栈 的大小
          p->size = 0;
          
          //初始化 栈中的元素
          for(int i=0; i<MAX ;i++){
              p->data[i] = 0;
          }
          
          return p;
          
      }
      
    • 入栈

      void push(SeqStack stack, int data){
          
          if(!stack){
              return;
          }
          
          //入栈 需要判断 栈是否已满
          if( stack->size == MAX ){
              printf("栈已满!\n");
              return;
          }
          
          // stack->size 栈的大小 就是 待插入元素 的下一个位置
          stack->data[stack->size] = data;
          
          //改变栈的大小
          stack->size++;
          
      }
      

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

    • 出栈

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

      出栈 需要注意 栈是否 不存在 and 已空。

    • 遍历 栈中元素

      void forerachStack(SeqStack stack){
          
          if(!stack) return;
          
          for(int i=0;i<stack->size; i++)
              printf("%d ",stack->data[i]);
          
      }
      

  • 栈 的 链式存储(链表)

    链表的头结点端 做 “栈顶” 还是 “栈底” 比较好?
    答:做 “栈顶” 比较好。

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

    • 栈链式存储 结构体

      //结点结构体
      trpedef struct Node{
          int data;
          struct Node *next;
      }Node;
      
      //栈的结构体
      trpedef struct Stack{
          
          struct Node header;			//头结点
          int size;					//栈的大小 即元素个数
          
      }Stack, *LinkStack;
      
    • 初始化栈

      LinkStack inir_LinkStack(){
          
          // myStack 是指针,指向所申请的空间
          LinkStack myStack = (Stack *)malloc(sizeof(Stack));
          
          if(!Stack)
              return NULL;
          
          //初始化 长度为0
          myStack->size = 0;
          
          //相当于 初始化 链表的头结点 指针域
          myStack->header.next = NULL;
          
      }
      

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

      -> 在指针的时候

      . 在变量名的时候

    • 注意事项

      struct Node *list;			list->next  list->next
      struct Node a;				a.data  a.next
      
    • 入栈

      void push(LinkStack stack, int data){
          
          if(!stack)
              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)
              return;
          
          //判断栈空
          if(stack->size ==0){
              printf("栈中无元素!\n");
              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, *SeqQueue;
      
    • 队列的初始化(数组头做队头,数组尾做队尾)

      SeqQueue init(){
      	
          //申请队列空间,即数组空间
          SeqQueue p = (Queue *)malloc(sizeof(Queue));
          
          if(!p){
              return NULL;
          }
          
          //初始化队列大小
          p->size = 0;
          
          for(int i=0; i<MAX ;i++){
              p->data[i] = 0;
          }
          
          reutrn p;
          
      }
      
    • 入队

      void push(SeqQueue p, int value){
          
          if(!p){				//队列不存在
              return;
          }
          
          if(p->size == MAX){
              printf("队列元素已满!\n");
              return;
          }
          
          //插入元素(进队)
          p->data[p->size] = value;
          
          //改变队列元素个数
          p->size ++;
          
      }
      
    • 出队

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

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

  • 队列的链式存储

    链表的 头结点端 做 “队头” 还是 “队尾” 比较好?
    答:做 “队头” 好。

    因为 头结点端 进行删除方便,尾端 插入元素方便(尾插法)。

    • 队列的结构体

      //队列的结构体
      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
    
    分别是:
        
    // *
    // *j
    // *jk
    // *jkj
    // *jkj1
    // *jkj12
    // *jkj12k
    // *jkj12k0
    // j
    // jk
    // jkj
    // jkj1
    // jkj12
    // jkj12k
    // jkj12k0
    // k
    // kj
    // kj1
    // kj12
    // kj12k
    // kj12k0
    // j1
    // j12
    // j12k
    // j12k0
    // 1
    // 12
    // 12k
    // 12k0
    // 2
    // 2k
    // 2k0
    // k0
    // 0
    // 空串
    
  • 真子串(不包含自身的子串)。

  • 主串:包含子串的串,称之为 主串。

  • 字符位置:字符在数组中的位置。

  • 子串位置:子串第一个字符串 在 主串中的位置。

  • 空格串:一个空格或者多个空格组成的串,称之为空格串。

  • 空串:零个没有任意字符的串,称之为空串。

    " "			// 空格串
    ""			// 空串
    
  • 串相等:当两个串长度相等,并且每个位置对应字符完全一样的时候,两个串才相等。

串的基本操作

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];		// 字符数组空间,MAX 为宏定义值
          int length;				// 串的长度
      };
      
  • BF算法

    // str 是主串	subStr 是模式匹配串
    int BF(char str[], char subStr[]){
    	
        int i=0,j=0;		//定义 i 主串下表,j 模式串下标
        
        while( i<strlen(str) && j<strlen(subStr) ){
            
            //如果对应位置相等,则下标后移,比较下一位元素
            if(str[i] == subStr[j]){
                i++;
                j++;
            } else{
                i = i-j+1;		//主串下标进行回溯
                j = 0;			//子串下标回溯
            }
            
        }
        
        if( j == strlen(subStr) ){		//子串下标 与 子串长度相等
            
            //返回子串位置
            return i - strlen(subStr) + 1;
            
        }
        
        return -1;		//未找到子串下标
        
    }
    

矩阵

  • 把矩阵当作 二维数组,只不过下标都是从 1 开始。

  • 二维数组 以行为主序存储,二维数组 以列为主序存储。

    以行存储,先去计算前 (i-1)行有多少元素
    以列存储,先去计算前 (j-1)列有多少元素
    

    前提:数组下标从一开始。

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

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

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

  • 三元组 (行,列,值) :

    (4,5,8)			//第四行,第五列,值为8	(前提:行列下标都从1开始)
    
    (4,5,8)			//第三行,第四行,值为8	(前提:行列下标都从0开始)
    

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

  • 树的性质

    1、树中的节点数  等于  所有节点的度数加12、度为 M 的树中,第i层上至多有 M的(i-1) 次方个节点(i>=1)3、高度为 H 的 M 叉树中,之多有 (M的H次方)/(M-1) 个节点。		// 二叉树 需要 再减去1:(M的H次方)/(M-1)-1
    
  • 二叉树:有0个 到 2个节点的树,称为 二叉树。

  • 二叉树的性质

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

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

二叉树链式存储

  • 结点的定义

    typedef struct TreeNode{
      
        char ch;					// 数据域
        struct TreeNode *lchild;		//左孩子 指针
        struct TreeNode *rchild;		//右孩子 指针
        
    }TreeNode;
    

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

  • 遍历方式

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

    void preOrder(Node *root){
        
        //递归结束条件,也就是说当一个节点 某个指针域为 NULL
        if(!root){
            return;
        }
        
        printf("%c ",root->ch);
        
        //递归调用左子树
        preOrder(root->lchild);
        
        //递归调用右子树
     	preOrder(root->rchild);
        
    }
    
  • 中序遍历

    void inOrder(Node *root){
        
        //递归结束条件,也就是说当一个节点 某个指针域为 NULL
        if(!root){
            return;
        }
        
        //递归调用左子树
        preOrder(root->lchild);
        
        printf("%c ",root->ch);
        
        //递归调用右子树
     	preOrder(root->rchild);
        
    }
    
  • 后序遍历

    void lastOrder(Node *root){
        
        //递归结束条件,也就是说当一个节点 某个指针域为 NULL
        if(!root){
            return;
        }
        
        //递归调用左子树
        preOrder(root->lchild);
        
        //递归调用右子树
     	preOrder(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) || (key == root->data) ){
        return root;
    } 
    else if( key < root->data )		// 递归左子树查找
    {
        retuen searchTreeNode(root->lchild, key);
    }
    else		//递归右子树查找
    {
        retuen searchTreeNode(root->rchile, key);
    }
    
}

哈夫曼树(最小权值树)

  • 路径:两个节点之间的 线 就是 路径。

  • 节点的路径:从 根节点 到 该节点,有几根线,路径长度就是几。

  • 树的路径长度:所有结点的路径长度之和,称之为树的路径长度。

  • 权值:给这个节点 赋一个数值,这个数值就是 权值。

  • 节点的带权路径长度:路径长度 * 该节点的权值。

  • 树的带权路径长度(WPL):所有 叶子节点 的带权路径长度 之和。

  • 重点:哈夫曼树构造

    //哈夫曼二叉树构造
    1、每次从 序列中找出 权值最小的两个节点(一般:从左至右,从小到大)
    2、生成一个新的节点,将这个节点放入 序列中,删除刚才的两个最小权值节点,再重复此过程。
    

邻接矩阵

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

邻接表

  • 防止浪费空间(稀疏矩阵),但是效率 相对于 邻接矩阵 较低。

  • 邻接表:顶点用 一维数组存储,后面跟着 单链表 存储邻接点。

  • 邻接表 中 邻接点的个数 和 图中这个顶点 的出度有关。

带权图

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

深度优先遍历 DFS

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

广度优先遍历算法

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

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

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

最小生成树

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

    • 生成树顶点个数 和 图顶点个数相同。

    • 生成树(极小连通图) 去掉一条边,则变成非连通图。

    • 连通图有 n 个顶点,则其 生成树 有 n-1条边。

    • 生成树 再加一条边,必然形成回路。

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

  • 无向图的生成树

    • 深度优先生成树
    • 广度优先生成树
  • 最小生成树:边权值之和最小的树。

普利姆算法(Prim)

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

克鲁斯卡尔算法(Kruskal)

  • 克鲁斯卡尔 和 有关。

  • 每次去找 边权值 最小的边,两个顶相连,依次构成最小生成树。

最短路径

迪杰斯特拉(Dijkstra)

  • 两点间最短路径。

弗洛伊德(Floyd)

  • 所有顶点间的最短路径。
  • 两个矩阵,一个存储 权值,一个存储 路径。
  • 把所有的顶点依次加入矩阵中,找最小权值路径。

拓扑排序

AOV网

  • 顶点 表示 活动。
  • 弧 表示活动之间的有限制约关系。
  • 拓扑排序:前提 有向无环图
  • 拓扑排序:找 入度为0(无前驱) 的结点 删除,循环上述过程即可。
  • 逆拓扑排序:找 出度为0(无后继) 的结点 删除,循环上述过程即可。
  • 判断 AOV网 中是否有环。
文章来源:https://blog.csdn.net/Discod_lim/article/details/129896690
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。