第四阶段学习:数据结构与算法

一、课程介绍

C基础,C高级:如何编写基础的程序;数据结构:组织维护数据的。数学:研究数字之间关系的学科,数字有操作,log tan + - * /;数据结构:研究数据之间关系的学科,数据(数字+非数字),数据之间有操作:增删改查,实现数据操作的代码,称为算法

  • 数据之间是有关系的

    • 线性关系:成员之间并列,平等,一对一关系,例如学生信息表
    • 树关系:从属关系,一对多关系
    • 图关系:成员之间任意连接的
  • 数据之间的关系:要从两个角度/维度去看

    • 逻辑关系:线性关系,树关系,图关系,从生活中出发,不考虑计算机情况
    • 存储关系:顺序存储、链式存储、哈希存储,了解研究计算机如何存储
  • 课程大纲

    • 线性关系
    • 树关系
    • 图关系
    • 算法:查找,排序
  • 课程特点

    • 概念多,代码量稍微大一点,逻辑性稍微强一点
    • 如何学:多画框,多练习,多用功
  • 算法:一段代码,实现特定的功能

    //比如1+2+3+.,.+N
    //方法1
    int sum fun (int N)
    {
        int total 0;
    	for(i=1;i<=N:i++)
      	{
         	total +=i;
      	}
      	return total;
    }
    //方法2
    int sum _fun (int N)
    {
        return  (1+N)*N/2;
    }
    
  • 衡量算法的好坏

    • 从时间角度考虑
      耗时的长短取决于数据的输入量,耗时是数据输入量的函数,T = fun(输入量)

      • 时间复杂度
        它是衡量一个算法时间消耗优劣的,如何求:首先你要知道语句频度。**当输入量N趋向于无穷的时候,语句频度的取值。**很多算法,他们的频度不一样,但是当输入量趋向于无穷的时候,时间复杂度只会有以下几种

        O(1) < O(1ogn) < O(n) < O(n1ogn) < O(n^2)
        

        QQ图片20230314090444

      • 语句频度
        算法中所有语句的执行次数累加和

        int bubble sort (int arr[],int N)
        {
        	int b;	//1次
        	for(int b =0;b <=N-2 ;b++)	//N-1次
            {
        		int i;	//N-1次
        		fox(i=0;i<=N-b-2;i++)	//(N-1)*N次
                {
        			if(arr[i+l]arr[i])	//(N-1)*N次
        			int temp=arr[i+1];	//(N-1)*N次
        			arr[i+1]=arr[i];	//(N-1)*N次
                    arr[i]=temp;		//(N-1)*N次
        		}
            }
            return 0;	//1次
        频度f = 2(N-1)+4(N-1)*N+1+1 = 2N+4N^2-4N = 4N^2-2N
        
    • 从空间角度考虑

二、线性关系

1、举例

​ 最常见,最重要的关系,生活中:队伍流水线;计算机:数组。

2、定义

​ 最多有一个前驱或一个后继的线性表,头部无前驱,尾部无后继

3、操作

​ 增删改查,遍历,求个数,清空,判空判满。

4、线性表(Linear List)

(1)数组

​ 逻辑上相邻,存储上也相邻。特点:一次要分配好,分配少了不能用,分配多了浪费空间,插入和删除比较麻烦。

QQ图片20230313110726

(2)单向链表(Singly linked list)

​ 链式结构,链表最重要的是头部,已知头部,可以获取每个元素。千万不要丢头部!!!

QQ图片20230313114254

例如:

#include <stdio.h>
#include <string.h>
//定义链表
struct node {
	char name[12];
	struct node *next;
};
//定义变量
struct node zhou,wu,zheng,wang;
int main(void)
{
    //链表赋值
    strcpy(zhou.name,"zhou");
    zhou.next = &wu;
    strcpy(wu.name,"wu");
    wu.next = &zheng;
    strcpy(zheng.name,"zheng");
    zheng.next=&wang;
    strcpy(wang.name,"wang");
    wang.next = NULL; 
	//遍历链表,并打印
	struct node *p = &zhou;
	while(  p  ){
		printf("%s ",p->name);
		p=p->next;
	}
	return 0;
}

a、增加

  • 空头链表:一个节点,作为链表的头部,不存放任何数据。

image-20230313153322447

例如:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//定义链表
struct node {
	char name[12];
	struct node *next;
};
//定义空头节点,头部pthead千万别动!!!
struct node head;
//定义空头节点头部、尾部
struct node *pthead = &head;
struct node *ptail = &head;
int main(void)
{
	struct node *p;
	// append list : add at tail
	while(1)
    {
		p = malloc(sizeoif( *p ));
		p->next = NULL; 
		printf("pls input name:");scanf("%s",p->name);
		if(  strcmp( p->name, "#" ) == 0 )
        {
			free(p);
			break;
		}
        //移动至下一个节点
		ptail->next = p;  
		ptail = ptail->next;
	}
	//遍历节点并打印
	p = pthead->next;
	while(p){
		printf("%s ",p->name);
		p=p->next;
	}
	printf("\n");
	return 0;
}

b、删除

  • 删除所查节点的后一个节点

    QQ图片20230313164630

  • 使用前后指针的方式删除所查节点

    image-20230313180050357

例如:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//定义链表
struct node {
	char name[12];
	struct node *next;
};
//定义空头节点,头部pthead千万别动!!!
struct node head;
//定义空头节点头部、尾部
struct node *pthead=&head;
struct node *ptail = &head;
//遍历链表并打印
void list_travel(struct node *pthead)
{
	struct node *p  = pthead->next;
	
	while(p){
		printf("%s ",p->name);
		p=p->next;
	}
	
	printf("\n");
	return;
}
//删除name节点后面的一个节点
int list_delete_node_behind(struct node *pthead,char *name  )
{
	/*find node, its name is #name# */
	struct node *p = pthead->next;
	while( p )
    {
		if(  strcmp(p->name, name) == 0  )
        {
			//find node ,p 就是你要找的
			struct node *q = p->next;  //下一个元素地址q
			struct node *m = q->next;  //下下个
			//删掉q,并释放
			p->next = m;	
			free(q);	
			return 0;
		}
		p=p->next;
	}
	//代码执行到这里说明了什么问题???
	//说明p==null
	return -11;
}
/*使用前后指针的方式删除当前name节点*/
int list_node_del(struct node *pthead, char *name)
{
	struct node *p=pthead->next;  	//后指针
	struct node *pre = pthead;		//前指针
	
	while(p)
   
		if( strcmp(p->name, name) == 0 )
        {
			//找到了,处理 ,pre是前一个节点 p是当前节点
			pre->next = p->next;
			free(p);
			return 0;
		}
		p=p->next;
		pre = pre->next;
	}
	//代码执行到这里说明了什么问题
	// p==null 
	return -12;
}
int main(void)
{
	struct node *p;
	// 尾部添加节点
	while(1)
    {
		p = malloc(sizeof( *p ));
		p->next = NULL; 
		printf("pls input name:");scanf("%s",p->name);
		if(  strcmp( p->name, "#" ) == 0 )
        {
			free(p);
			break;
		}
		ptail->next = p;  
		ptail = ptail->next;
	}
	//遍历链表节点并打印
	printf("create done: ");
	list_travel(pthead);
	//删除节点
	list_delete_node_behind(pthead,"wu"  );
	printf("detele done:");
    //再次遍历链表节点并打印
	list_travel(pthead);
	printf("\n");
	return 0;
}

c、插入

image-20230313180323418

//在name节点的后面,插入一个新的节点 名字为insertname
int list_insert_node(struct node *pthead, char *name, char *insertname)
{
	//遍历list找到name的节点m 
	struct node *m = pthead->next;
	while(m)
    {
		//找到name的节点
		if(strcmp(m->name,name) == 0)
        {
			//找到了m, 开始插入 新节点
			struct node *p = malloc(sizeof(*p));
			strcpy(p->name,insertname);  p->next = NULL;
			//在m的后面插入p 
			p->next = m->next;
			m->next = p;
			return 0; //结束了
		}
		m=m->next;
	}
	//为什么代码到这里.....    m==NULL
	return -12;	
}

总结

空间操作
数组一次分配,连续删除/插入很麻烦,支持随机访问,利用率高
链表按需分配,不连续删除/插入简单,不支持随机访问,利用率较低
(3)双向链表(Doubly linked list)

​ 可以双向(正向、反向)遍历各个节点

a、增加

QQ图片20230314102502

例如:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//定义双向链表
struct dnode 
{
    char name[12];
    struct dnode *pre;
    struct dnode *next;
};
//定义空头节点
struct dnode head;
struct dnode *pthead = &head;
struct dnode *ptail = &head;
//遍历所有节点并打印
void list_travel(struct dnode *pthead,struct dnode *ptail)
{
	struct dnode *p=pthead->next;
    //1、正向遍历
	printf("travel forward:");
	while(p)
    {
		printf("%s ",p->name);
		p=p->next;
	}
	printf("\n");
    //2、反向遍历
	printf("travel back:");
	//其实p=ptail,因为ptail有元素需要打印。当p抵达头节点的时候,不要打印,结束循环
	p=ptail;
	while( p!=pthead )
    {
		printf("%s ",p->name);
		p=p->pre;
	}
	printf("\n");
}
int main(void)
{
    //追加节点 ,得到一个完整的链表
    while(1)
    {
        struct dnode *p = malloc(sizeof (*p));
        p->next = p->pre = NULL;
        printf ("pls input name:");
        scanf("%s",p->name);
        if(  strcmp(p->name,"#")==0  )
        {
			free(p);
			break;
		}
        //开始追加节点
        ptail->next = p;
        p->pre = ptail;
        ptail = ptail->next;
    }
    //正向、反向遍历所有节点并打印
    list_travel(pthead,ptail);
    return 0;
}

b、删除

image-20230314114413537

例如:

int list_del(struct dnode *pthead)
{
	//遍历,找到这个人
    char name[12] = {0};
    printf("pls input del name:");
    scanf("%s",name);
    struct dnode *p=pthead->next;
    while(p)
    {
        if(strcmp (p->name,name)==0)
        {
			//找到了,删了p
			struct dnode *m=p->pre;
			struct dnode *n=p->next;
            m->next = n;
            n->pre = m;
            free(p);
            return 0;
        }
        p = p->next;
    }
    //代码到这里,说明 p == NULL
    printf("no found\n");
    return -1;        
}

c、插入

image-20230314114500277

例如:

int list_insert_before(struct dnode *pthead)
{
    //首先用户输入要查找的人名
    char findname[12] = {0};
    printf("pls input findname:");
    scanf("%s",findname);
    //找到这个findname的节点p
    struct dnode *p = pthead->next;
    while(p)
    {
		  if(strcmp (p->name,findname)==0)
        {
			 //找到了,然后在p前面插入新节点
             struct dnode *pre = p->pre;
             //新节点
             struct dnode *q = malloc(sizeof(*q));
             q->pre = q->next = NULL;
             printf("input insert name:");
			 scanf("%s",q->name);
             //插入新节点q
			 pre->next=q; 
             q->pre=pre;
			 q->next=p; 
             p->pre=q;
             return 0;
          }
        p = p->next;
    }
    //p是空的,没有找到 
	printf("find name not found\n");
	return -1;
}
(4)循环链表

​ 分为单向循环链表和双向循环链表。单向循环链表的尾节点ptail的next域存储了头节点pthead(ptail->next=pthead);双向循环链表的尾结点ptail的next域存储了头节点pthead(ptail->next=pthead),头节点pthead的pre域存储了尾节点ptail(pthead->pre=ptail)

QQ图片20230314140922

(5)栈(Stack)

​ 是一种先进后出的线性表,栈的基础功能就是逆序(栈只允许在一端进行插入和删除)。先进后出(FILO/LIFO):fisrt input last output / last input first output

a、数组实现栈
空间要求一次分配,可以实现先进后出FILO。

QQ图片20230314150105

#include <stdio.h>
//声明栈
struct stack
{
	#define N  8
	int arr[N];
	int top;
};
//定义栈变量
struct stack seqstack;
//栈初始化
void seq_stack_init(struct stack *pstack)
{
	pstack->top = 0;		//初始化为0,表示当前没有数据
}
//向栈存入数据
int seq_stack_push(struct stack *pstack,int val)
{
    //判满
    if( pstack->top == N )
    {
        printf("push error,full now\n");
		return -22;
    }
    //推入数据
	pstack->arr[  pstack->top ] = val;
	pstack->top++;
	return 0;
}
//从栈取出数据
int seq_stack_pop(struct stack *pstack)
{
	int val;
    //判空
    if(pstack->top == 0)
    {
        printf("sorry ,empty now\n");
        return -23;
    }
	pstack->top--;
	val = pstack->arr[  pstack->top ];
	return val;
}
int main(void)
{
	int val;
	//初始化栈
	seq_stack_init(&seqstack);
	//推入数据
	for(int i=7;i<=12;i++)
    {
		seq_stack_push( &seqstack ,i);
	}
	//取出数据
	for(int i=1;i<=6;i++)
    {
		val = seq_stack_pop(&seqstack);
		printf("%d ",val);
	}
}

b、链表实现栈
只在链表头部进行插入和删除操作,同样可以实现先进后出FILO。

#include <stdio.h>
#include <stdlib.h>
//声明栈
struct node 
{
	int data;
	struct node *next;
};
//定义栈变量
struct node head;
//向栈存入数据
int list_stack_push( struct node *pthead , int val )
{
	//分配一个节点p,将p插入到head后面即可
	struct node *p = malloc(sizeof(*p));
	p->next = NULL; 
    p->data = val;
	//插入到头部后面!!!
	p->next = pthead->next;
	pthead->next = p;
	return 0;
}
//从栈取出数据
int list_stack_pop(struct node *pthead)
{
    //判空
	if(pthead->next == NULL)
    {
		return 0xFF;
	}
    //读p中数据
	struct node *p=pthead->next;
	int val = p->data;
	//删除p
	pthead->next = p->next;
	free(p);
    //返回p中值
	return val;
}
int main(void)
{
	//依次推入 7-19
	for(int i = 7;i<20; i++)
    {
		list_stack_push( &head , i );	
	}
	//取出数据
	for(int i=0;i<100; i++)
    {
		int val = list_stack_pop(&head);
		if(val == 0xFF )
        {
			printf("sorry,emppyt now\n");
			break;
		}
		printf("%d ",val);
	}
	return 0;
}

注意
全局变量或全局函数的默认值为int类型的 0。当函数未指定返回类型时,返回值类型为int类型

int function(){};	//全局函数
int a;	//全局变量
int main()
{
	printf("a = %d",a);	//0
    printf("function() = %d",function());	//0
}
(6)队列(Queue)

只允许在两端进行插入和删除操作的线性表,队尾插入,队头删除。先进先出(FIFO):first input first output,功能:起缓冲作用

a、数组实现队列
环形队列

QQ图片20230314180509
#include <stdio.h>
//声明队列
struct queue 
{
	#define N  8
	int arr[N];
	int r;
	int w;
};
//定义队列变量
struct queue que;
//对列初始化
void queue_init(struct queue *pque)
{
	pque->r = pque->w = 0;
}
//向队列存入数据
int queue_push(struct queue *pque  , int val)
{
	//判满 
	if(   ( (pque-> w + 1)%N )    ==  pque->r )
    {
		return 0xFF;
	}
	pque->arr[  pque->w  ] = val;
	pque->w = (pque->w +1) % N;     //如果w==N,则自动归零
	return 0;
}
//从队列取出数据
int queue_pop(struct queue *pque  )
{
	//判空
	if(pque->w == pque->r )
    {
		return 0xFF;
	}
	int val = pque->arr[  pque->r ];
	pque->r = (pque->r+1)%N;	//如果r==N,则自动归零
	return val;
}
int main(void)
{
	//初始化队列
	queue_init(&que);
    //存入数据
	for(int i=7;i<20; i++)
    {
		int ret = queue_push(&que , i);
		if(ret == 0xFF)
        {
			printf("full now\n");
			break;
		}
	}
    //读取数据
	for(int i=0;i<20;i++)
    {
		int val = queue_pop(&que);
		if(val==0xFF)
        {
			printf("empyt now\n");
			break;
		}
		printf("%d  ",val);
	}
	printf("\n");
}

b、链表实现队列
实现一个链表,在队尾插入,在队头删除,即可实现FIFO

QQ图片20230315102404

#include <stdio.h>
#include <stdlib.h>
//定义链表
struct node {
	int data;
	struct node *next;
};
//定义变量
struct node head;
struct node *pthead = &head;
struct node *ptail = &head;
//队列初始化
void queue_init(struct node *phead)
{
	phead->next = NULL;
}
//向队列存入数据
void queue_push( struct node *pthead ,int val)
{
	struct node *p=malloc(sizeof(*p));
	p->data = val; p->next=NULL;
	//追加到尾巴
	ptail->next = p;
	ptail=ptail->next;
	return ;
}
//从队列取出数据
int queue_pop(struct node *pthead)
{
	struct node *p = pthead->next;
	//判空 
	if(pthead->next == NULL)
    {
		return 0xFF;
	}
    //从头部取出数据
	int val = p->data;
	//删掉p 
	pthead->next = p->next;
	free(p);
	if(p==ptail)	//注意事项
    {	
		ptail=pthead;//回到最初始的状态.	
	}
	return val;
}
int main(void)
{
	//初始化队列
	queue_init(&head);
	//入列
	for(int i = 7;i< 20;i++)
    {
		queue_push( pthead , i);
	}
	//出列
	for(int i=0;i<=100;i++)
    {
		int val = queue_pop(pthead);
		if(val==0xFF)
        {
			printf("queue pop finish\n");
			break;
		}
		printf("%d  ",val);
	}
	return 0;
}
(7)树

有且只有一个根,有很多分支/子树,互不相交。例如:族谱、公司架构、目录

a、概念

  • 根节点(root):树的起源
  • 深度:树的层数
  • :某节点所在层数
  • 度数:某节点的直接子树的个数
  • 父子节点:两个节点属于父子关系
  • 兄弟节点:他们拥有同一个父亲

QQ图片20230315110537

b、二叉树
每个节点最多只有两个直接子节点,计算机中一般研究满二叉树。

  • 满二叉树:除了最后一层叶子节点,其他节点都有两个子节点
    QQ图片20230315111217

  • 完全二叉树:满二叉树摘掉几个叶子节点,且叶子节点都集中在最左边,就是完全二叉树。

    例如:
    若设二叉树的深度为h,除第 h 层外其它各层 (1~h-1) 的结点数都达到最大个数(即1~h-1层为一个满二叉树),第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
    image-20230320141201796

c、二叉树的存储

  • 数组存储

    ​ 将二叉树补全为满二叉树,然后编号,按照编号依次存入数组中。缺点:浪费巨大的空间。

  • 链式存储
    使用链表存储
    QQ图片20230315165929

d、二叉树遍历
每个元素访问且只访问一次

  • 三种遍历方法
    具有很强的递归特性

    • 先序遍历
      ”根左右“,先遍历根,然后左边,最后右边
    • 中序遍历
      “左根右”,先遍历左边,然后根,最后右边
    • 后续遍历
      “左右根”:先遍历左边,然后右边,最后根

    例如:

    QQ图片20230315154223
    先序遍历:A B C D E F G H K
    中序遍历:B D C A E H G K F
    后续遍历:D C B H K G F E A
    

e、二叉树反推

  • 已知“先序,中序”,反推画出树图,从而可以求出后序

    • 先序:根在前面

    • 中序:根在中间,左边的在根前面,后面的在根后面。

    • 总结
      “先序知根,中序分左右”

    • 例如
      已知先序:0 1 3 7 8 4 9 2 5 6,中序:7 3 8 1 9 4 0 5 2 6,求树图

      • (1)先序找根,中序定两边
        先序的特点是第一个元素是根确定0是根节点,中序的特点是根两侧分别是左右子树确定7 3 8 1 9 4 在0左边,5 2 6在0右边
        image-20230315162908463

      • (2)左右分别重复(1)操作

        image-20230315163031332

      • (3)不停的重复(1)操作, 最终的二叉树图为:
        image-20230315163411591

  • 已知“中序,后序”,反推画出树图,从而可以求出先序
    QQ图片20230315163610

f、链表实现二叉树
QQ图片20230315165929

例如:

#include <stdio.h>
//定义双向链表
struct node
{
	char ch;
	struct node *left,*right;
};
//定义节点变量
struct node a,b,c,d,e,f,g;
//先序遍历 递归
void btree_travel_front(struct node *root)
{
	if(root==NULL)
    {
		return;
	}
	printf("%c ",root->ch);
	btree_travel_front(root->left);
	btree_travel_front(root->right);
}
//中序遍历 递归
void btree_travel_middle(struct node *root)
{
	if(root==NULL)
    {
		return ;
	}
	btree_travel_middle(root->left);
	printf("%c ",root->ch);
	btree_travel_middle(root->right);
}
//后序遍历
void btree_travel_after(struct node *root)
{
	if(root==NULL)
    {
		return ;
	}
	btree_travel_after(root->left);
	btree_travel_after(root->right);
	printf("%c ",root->ch);
}
int main(void)
{
    //初始化二叉树
	a.left = &b;  a.ch = 'A';  a.right = NULL;
	b.left = &c;  b.ch = 'B';  b.right = &d;
	c.left = NULL;c.ch = 'C';  c.right = NULL;
	d.left = &e;  d.ch = 'D';  d.right = &f;
	e.left = NULL;e.ch = 'E';  e.right = &g;
	f.left = NULL;f.ch = 'F';  f.right = NULL; 
	g.left = NULL;g.ch = 'G';  g.right = NULL;
	//根节点
	struct node *root  = &a;
	//先序遍历代码
	btree_travel_front(root);   printf("\n");
	//中序遍历代码
	btree_travel_middle(root);   printf("\n");
	//后续遍历
	btree_travel_after(root);	 printf("\n");
	return 0;
}

g、递归函数
函数调用自己实现功能,递归函数是一个循环,必须有结束条件。
递归函数

例如:

#include <stdio.h>
//1、求1+2+..n的结果
int recursive_fun_sum(int n)
{
	if(n==1)
    {
		return 1;
	}
	return n+recursive_fun_sum(n-1);
}
/* 2、求阶乘
fun(n) = fun(n-1) *n  
		= fun(n-2) (n-1) n 
		= fun(n-3) (n-2) (n-1) n 
		  ......
		= fun(1) *2*3*.....*n
*/
int recursive_fun_jiecheng(int n)
{
	if(n==1)
    {
		return 1;
	}
	return recursive_fun_jiecheng(n-1) *n;
}
int main(void)
{
	int val = recursive_fun_jiecheng(5);
	printf("jiecheng result = %d\n",val);
	val = recursive_fun_sum(100);
	printf("sum = %d\n ",val);
	return 0;
}

5、图

类型描述
线性表每个节点最多有一个前驱,一个后继
每个节点最多只有一个前驱,可以有多个后继
可以有多个前驱,可以有多个后继,例如:人际关系网,计算机网络

QQ图片20230316093527

(1)图的存储

a、二维数组/矩阵
缺点:太浪费空间

b、链表存储
记录每个节点相连的其他节点,以及他们的距离

QQ图片20230316103851

struct node
{
	char name [12];
	int distance;
	struct node *next;
}
//存放所有头节点
struct node head [10];
//例如取出第二个头
struct node *pthead = &head[1];
(2)图的遍历

每个节点访问且只访问一次。需要每个节点有一个标记,用来表示该节点是否己经被遍历。

a、深度遍历(DFS)
一直往里面走,如果发现走不通,则往后退,然后重新找路往里面走。在遍历的过程中,每个节点只会被访问一次。DFS:Depth-First Search

b、广度遍历(BFS)
从图中某个节点开始,逐层遍历其它节点,直到所有能够到达的节点都被遍历完为止。在遍历的过程中,每个节点只会被访问一次。BFS:Breadth-First Search
总结
深度遍历和广度遍历都是图遍历算法中常用的方法。深度遍历算法适合用于查找路径以及判断图的连通性等问题,而广度遍历算法适合用于寻找最短路径以及拓扑排序等问题

例如:(下图答案均不唯一!
QQ图片20230316111637

(3)查找和排序

a、查找
根据关键字(名字学号)在指定集合中寻找数据的过程

  • 查找方法

    • 遍历
      从头到尾,直到找到数据为止。遍历的时间复杂度 t=O(n)。优点:简单,易理解;缺点:数据量大的时候耗时。场景:数据量小,无序的数据

    • 二分查找(折半查找)
      场景:有序数据。遍历的时间复杂度 t=O(logN)

      #include <stdio.h>
      int arr [] = {12,34,56,78,99,101,129,150,189,190};
      //二分查找
      int bin_find(int findval)
      {
      	int low = 0;
      	int high = sizeof(arr)/sizeof(int) - 1;
      	
      	while( low <=high )
          {
      		int mid=(low+high)/2;
      		if( arr[mid]==findval)
              {
      			return mid;		//找到了,结束
      		}
      		if(arr[mid] < findval )
              {
      			low = mid+1;
      		}
      		if(arr[mid] > findval)
              {
      			high=mid-1; 
      		}
      	}
      	//代码这里 low>high 
      	return -1;
      }
      int main(void)
      {
      	int findval;
      	while(1)
          {
      		printf("input findval:");
      		scanf("%d",&findval);
      		int pos =  bin_find(findval);
      		printf("pos=%d\n",pos);
      	}
      }
      
    • 哈希查找(hash)
      哈希值,使用哈希函数求,它能将任意输入转换为整数。哈希查找时间一般不随数据量变化,而且是直接给你结果。
      QQ图片20230316154757

    例如:(学生管理系统

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    //声明链表
    struct node 
    {
    	char name[16];
    	char sex;
    	int age;
    	int id;
    	struct node *next;
    };
    //定义链表数组,用来存放哈希值空头节点
    struct node head_arr[2000];
    //简单的哈希算法,将字符按照ASCII码值相加,从而求出哈希值
    int hash_fun(char *name)
    {
    	int sum=0;
    	for(int i=0; i<strlen(name);i++)
        {
    		sum += name[i];
    	}
    	return sum %2000;   //将哈希值限制在0-1999
    }
    //1、添加学生
    int student_add(void )
    {
    	//分配空间,存储数据
    	struct node *p=malloc(sizeof(*p));
    	p->next = NULL;
    	printf("pls input name sex age id:");
    	scanf("%s %c %d %d",p->name,&p->sex,&p->age,&p->id);
    	//寻找合适的位置 
    	int hash_val = hash_fun(p->name);
    	struct node *pthead = & head_arr[hash_val];
    	//插入指定位置 (建议头部后面)
    	p->next = pthead->next;
    	pthead->next = p;
    	return 0;
    }
    //2、删除学生
    int student_del()
    {
    	printf("请输入要删除的学生姓名:");
    	char name[16];
        scanf("%s",name);
    	int hash_val = hash_fun(name);
        //前后指针
    	struct node *p = (&head_arr[hash_val])->next;
    	struct node *pre = &head_arr[hash_val];
    	while(p)
    	{
    		if( strcmp(p->name, name) == 0 )
    		{
    			pre->next = p->next;
    			free(p);
    		}
    		p = p->next;
    		pre = pre->next;
    	}
        printf("sorry,delete error\n");
    	return -1;
    }
    //3、修改学生信息
    int student_modify()
    {
    	 printf("请输入要修改的学生姓名:");
         char name[16];
         scanf("%s",name);
         int hash_val = hash_fun(name);
         //前后指针
         struct node *p = (&head_arr[hash_val])->next;
    	 struct node *pre = (&head_arr[hash_val]);
    	 while(p)
         {
    		 if( strcmp(p->name, name) == 0 )
             {
    			struct node *q = malloc( sizeof(*q));
    			q->next = NULL;
    			printf("pls input name sex age id:");
            	scanf("%s %c %d %d",q->name,&q->sex,&q->age,&q->id);
    			int hash_val_q = hash_fun(q->name);
            	struct node *pthead = & head_arr[hash_val_q];
            	//按哈希值,插入新位置 (建议头部后面)
            	q->next = pthead->next;
            	pthead->next = q;
    			//删除原有p
    			pre->next = p->next;
    			free(p);
    		 }
    		 p = p->next;
    		 pre = pre->next;
    	}
        printf("sorry,modify error\n");
    	return -1;
    }
    //4、查询学生
    int student_look()
    {
    	char name[16];
    	printf("请输入要查询的学生姓名:");
    	scanf("%s",name);
    	int hash_val = hash_fun(name);
    	struct node *p = (&head_arr[hash_val])->next;
    	while(p)
    	{
    		if( strcmp(p->name, name) == 0 )
    		{
    			 printf("%s %c %d %d\n",p->name,p->sex,p->age,p->id);
    		}
    		p = p->next;
    	}
    	printf("sorry,not find\n");
    	return -1;
    }
    int main(void)
    {
    	int select;
    	while(1)
    	{
    		printf("welcome to student manage system\n");
    		printf("1.add student \n2.delete student \n3.modify \n4.find student\n");
    		scanf("%d",&select);
    		switch(select)
    		{
                case 1:
                    student_add();
                    break;
                case 2:
                    student_del();
                    break;			
                case 3:
                    student_modify();	
                    break;
                case 4:
                    student_look();	
                    break;	
                default:
                    printf("输入错误,请重新输入\n");
    		}	
    	}	
    }
    

6、排序

(1)选择排序

每次从待排序的数组中选出最小的元素,将其放到已排序数组的末尾,然后继续在剩余的未排序数组中重复这个过程,直到所有元素都被排序。选择排序的时间复杂度为O(n^2),效率比其他排序算法要低,所以不推荐在大规模数据排序时使用选择排序。
选择排序

QQ图片20230317105723

例如:

#include <stdio.h>
int arr[]={45,69,32,58,45,128,35,496,21,45,436,865,55};
//遍历并打印数组
void travel_arr(int arr[],int N)
{
    for(int i=0;i<N ;i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
}
//1、选择排序
int selection_sort(int arr[],int N)
{
   for(int bian=0;bian<N-1;bian++)	//最后一遍是自己与自己比较,因此可以不用判断bian=N-1
   {
      //某遍从bian开始到N-1,找出最小值的位置minipos
      //假设起始就是最小位置minipos=bian
      int minipos = bian;
      for(int j=bian;j<=N-1;j++)
      {
           if( arr[j]<arr[minipos] )
           {
               minipos = j;
           }
      }
       //这里,minipos肯定是最小值的位置
       //要和起始值交换
      int temp=arr[minipos];arr[minipos]=arr[bian];arr[bian]=temp;
   }
   return  0;
}
int main(void)
{
    selection_sort(arr,sizeof(arr)/sizeof(int));
    travel_arr(arr,sizeof(arr)/sizeof(int));
    return 0;
}
(2)冒泡排序

从数组的第一个元素开始,依次比较相邻的两个元素的大小关系,如果前一个元素大于后一个元素,则交换它们的位置。这样一轮比较下来,最大的元素就会被交换到数组的末尾。然后,从数组的第一个元素开始,重复以上步骤,直到所有元素都被排序。冒泡排序的时间复杂度为O(n^2),不太适用于大规模数据的排序。
冒泡排序

QQ图片20230317112917

例如:

#include <stdio.h>
int arr[]={45,69,32,58,45,128,35,496,21,45,436,865,55};
//遍历并打印数组
void travel_arr(int arr[],int N)
{
        for(int i=0;i<N ;i++)
        {
                printf("%d ",arr[i]);
        }
         printf("\n");
}
//2、冒泡排序
int bubble_sort(int arr[],int N)
{
    for(int i=0; i<N-1; i++)
    {
        //第i遍,j从0走到N-i-2,依次比较j和j+1
        //如果发现arr[j]>arr[j+1],交换
        for(int j=0;j<=N-i-2;j++)
        {
            if( arr[j] > arr[j+1] )
            {
                //交换
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
    return 0;
}
int main(void)
{
    bubble_sort(arr,sizeof(arr)/sizeof(int));
    travel_arr(arr,sizeof(arr)/sizeof(int));
    return 0;
}
(3)快速排序

选取一个元素作为基准值(一般选数组第0个元素作为基准值),将数组分为两部分。通过特殊的方法,让基准值左边的都小于它,右边的都大于它,那么该基准值就定序。然后对左、右两部分分别进行相同的操作,直到每个元素都定序,此时整个数组有序。快速排序的时间复杂度为O(nlogn),快速排序是一种高效的排序算法,因此在实际应用中被广泛使用。

  • 关键点
    找参考值,然后 使用某种方法,让参考值左边的小于他,右边的大于它
    快速排序
    注意
    快速排序时,一定要让基准值对面的先走!!!
    例如:
    下图第一次排序时,基准值为6,一定要让对面的j先走,然后i再走,否则会出错!
QQ图片20230317152134
#include <stdio.h>
int arr[]={45,69,32,58,45,128,35,496,21,45,436,865,55};
//遍历并打印数组
void travel_arr(int arr[],int N)
{
    for(int i=0;i<N ;i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
}
//3、快速排序
int quick_sort(int arr[],int start,int end)
{
    int i = start;  //小兵i就位
    int j = end;    //小兵j就位
    int key = arr[start];   //选择数组第0个元素为基准值
    //递归的结束条件
    if( start >= end )
    {
        return 0;
    }
    while( j>i )  //如果i和j没有相遇,则j往前走,i往后走
    {
        //基准值对面的j先走!!!
        while( arr[j]>=key && j>i )
        {
            j--;
        }
        //此处说明小兵j找到了一个小于key的值
        //然后再让小兵i走
        while( arr[i]<=key && i<j )
        {
            i++;
        }
        //此处说明小兵i找到了一个大于key的值
        //交换i和j的值
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    //代码到了这里,说明i==j,交换 start 和 i/j 的位置
    int temp1 = arr[start];
    arr[start] = arr[i];
    arr[i] = temp1;
    //至此,走了一遍,定序了 i/j 的位置,值为key
    //接下来要对 i/j 的左侧进行相同的操作
    quick_sort(arr,start,i-1);  //左边部分
    //还要对 i/j 的右侧进行相同的操作
    quick_sort(arr,j+1,end);    //右边部分
    return 0;
}
int main(void)
{
    quick_sort(arr,0,sizeof(arr)/sizeof(int)-1);
    travel_arr(arr,sizeof(arr)/sizeof(int));
    return 0;
}
(4)逆序
  • 数组的逆序:首尾元素互换

  • 链表的逆序

    • 单向链表的的逆序:头插法
      image-20230320162816825

      //链表节点反转(头插法)
      void node_reverse(struct node *pthead)
      {
          struct node *p = pthead->next;
          pthead->next = NULL;
          struct node *q;	//用来作为剩下链表的头部
          while( p )
          {
              q = p->next;
              p->next = pthead->next;
              pthead->next = p;
              p = q;
          }
      }
      
    • 双向链表的的逆序:头插法

QQ图片20230317163601

//4.1、整型数组逆序排列
int reverse_arr(int arr[],int N)
{
    int i = 0;
    int j = N-1;
    while( j > i )	//不管数组元素个数是奇数还是偶数,只要 i>=j 就结束
    {
        //交换首尾元素
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
        i++;
        j--;
    }
    return 0;
}
//4.2、字符数组逆序排列
int string_reverse(char *str)
{
    int i = 0;
    int j = 0;
    //将j定位到最后一个字符,求字符串长度
    while( str[j] != '\0' )
    {
        j++;
    }
    //这里说明 str[j] == NULL
    j--;
    //开始交换
    while( j > i )	//不管数组元素个数是奇数还是偶数,只要 i>=j 就结束
    {
        //交换首尾元素
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
        i++;
        j--;
    }
    return 0;
}
/*
注意:
char *str = "Hello World";
此时该字符串存储在常量区,常量区只读不可写,不能这样操作:str[0] = 'C';
char *str[] = "Hello World";
此时该字符串局部变量存储在栈区,栈区可读可写,可以这样操作:str[0] = 'C';
*/

7、链表常用函数

//定义链表
typedef struct linklist
{
    data_t data;    //数据域
    struct linklist *next; //指针域
}Node,LinkList;
//1、逆序函数
void reverse(LinkList *head)
{
    Node *p = head->next->next;//从第二个结点开始
    Node *q = NULL;
    head->next->next = NULL;//将第一个结点变为尾结点
    while(p != NULL)
    {
        q = p->next;
        p->next = head->next;
        head->next = p;
        p = q;
    }
}
//2、创建节点
LinkList *Create_Linklist()
{
    //创建节点
    LinkList *head = (LinkList *)malloc(sizeof(LinkList));
    if(head == NULL)
    {
        printf("head malloc failed!\n");
        return NULL;
    }
    //节点next指向空
    head->next = NULL;
    return head;
}
//3、打印内容
void Show_Linklist(LinkList *head)
{
    Node *p = head;
    if(p->next == NULL)
    {	//判空
        printf("Empty!ERROR!\n");
        return;
    }
    while(p->next != NULL)
    {
        p = p->next;
        printf("%d ",p->data);
    }
    puts("");
}
//4、判空
bool Linklist_Is_Empty(LinkList *head)
{
    if(head->next == NULL)
    {
        return true;//返回 真
    }else
    {
        return false;//返回 假
    }
}
//5、计算表长
int Linklist_Length(LinkList *head)
{
    Node *p = head;
    int count = 0;
    while(p->next != NULL)
    {
        count++;
        p = p->next;
    }
    return count;
}
//6、头插法
void Linklist_Insert_Head(LinkList *head,int data)
{
    Node *new = (Node *)malloc(sizeof(Node));//申请新空间
    new->data = data;
    new->next = head->next; //新节点 指向 头节点后的节点
    head->next = new; //新节点插入头节点后
}

//7、插入(按位置)
void Linklist_Insert_Pos(LinkList *head,int pos,int data)
{
    if(pos <= 0 || pos > Linklist_Length(head)+1)
    {
        printf("Insert pos ERROR!\n");
        return;
    }
    Node *p = head;
    Node *new = (Node *)malloc(sizeof(Node));
    new->data = data;
    int i = 0;
    while(++i < pos)
    {
        //p指向pos前一个位置
        p = p->next;
    }
    new->next = p->next;
    p->next = new;
}
//8、删除 按位置
void Linklist_Delete_Pos(LinkList *head,int pos)
{
    if(pos<=0 || pos > Linklist_Length(head))
    {
        printf("Delete Pos ERROR!\n");
        return;
    }
    Node *p = head;
    Node *q = NULL;
    int i = 0;
    while(++i < pos)
    {
        //p指向pos前一个位置
        p = p->next;
    }
    q = p->next;
    p->next = q->next;
    free(q);//释放
}

//9、删除 按数据
void Linklist_Delete_Val(LinkList *head,int data)
{
    if(Linklist_Is_Empty(head))
    {
        //判断空表
        printf("Is Empty List!\n");
        return;
    }
    Node *p = head->next;
    int i,j=0;
    for(i = 1; i <= Linklist_Length(head); i++)
    {
        if(p->data == data)
        {
            //找到 位置
            Linklist_Delete_Pos(head,i);
            j = 1;
            break;
        }
        else
        {
            p = p->next;
        }
    }
    if(j)
    {
        printf("Data is not exist!\n");
        return;
    }
}
//10、查找 按位置 返回数据
data_t Linklist_Search_Pos(LinkList *head,int pos)
{
    if(pos <= 0 || pos > Linklist_Length(head))
    {
        printf("Search Pos ERROR!\n");
        return -1;
    }
    Node *p = head;
    int i = 0;
    while(++i <= pos)
    {
        //p指向查找的位置
        p = p->next;
    }
    return p->data;
}
//11、查找 按数据 返回位置
int Linklist_Search_Val(LinkList *head,int data)
{
    if(Linklist_Is_Empty(head))
    {//判断空表
        printf("Link Is Empty!ERROR!\n");
        return -1;
    }
    Node *p = head->next;
    int i;
    for(i = 1; i <= Linklist_Length(head); i++)
    {
        if(p->data == data)
        {
            return i;
        }
        else
        {
            p = p->next;
        }
    }
    printf("Data is not exist!ERROR!\n");
    return -1;
}
//12、修改 按位置
void Linklist_Change_Pos(LinkList *head,int pos,int data)
{
    if(pos <= 0 || pos > Linklist_Length(head))
    {
        printf("Change Pos ERROR!\n");
        return;
    }
    Node *p = head;
    int i = 0;
    while(++i <= pos)
    {
        //p指向pos位置
        p = p->next;
    }
    p->data = data;
}
//13、修改 按数据
void Linklist_Change_Val(LinkList *head,int o_data,int n_data)
{
    if(Linklist_Is_Empty(head))
    {
        //判断空表
        printf("List Is Empty!ERROR!\n");
        return;
    }
    Node *p = head->next;
    int i;
    for(i = 1; i <= Linklist_Length(head); i++)
    {
        if(p->data == o_data)
        {
            p->data = n_data;
            return;
        }
        else
        {
            p = p->next;
        }
    }
    printf("Data is not exist!ERROR!\n");
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值