一、课程介绍
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)
-
语句频度
算法中所有语句的执行次数累加和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)数组
逻辑上相邻,存储上也相邻。特点:一次要分配好,分配少了不能用,分配多了浪费空间,插入和删除比较麻烦。
(2)单向链表(Singly linked list)
链式结构,链表最重要的是头部,已知头部,可以获取每个元素。千万不要丢头部!!!
例如:
#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、增加
- 空头链表:一个节点,作为链表的头部,不存放任何数据。
例如:
#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、删除
-
删除所查节点的后一个节点
-
使用前后指针的方式删除所查节点
例如:
#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、插入
//在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、增加
例如:
#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、删除
例如:
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、插入
例如:
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)。
(5)栈(Stack)
是一种先进后出的线性表,栈的基础功能就是逆序(栈只允许在一端进行插入和删除)。先进后出(FILO/LIFO):fisrt input last output / last input first output
a、数组实现栈
空间要求一次分配,可以实现先进后出FILO。
#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、数组实现队列
环形队列
#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
#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):树的起源
- 深度:树的层数
- 层:某节点所在层数
- 度数:某节点的直接子树的个数
- 父子节点:两个节点属于父子关系
- 兄弟节点:他们拥有同一个父亲
b、二叉树
每个节点最多只有两个直接子节点,计算机中一般研究满二叉树。
-
满二叉树:除了最后一层叶子节点,其他节点都有两个子节点
-
完全二叉树:满二叉树摘掉几个叶子节点,且叶子节点都集中在最左边,就是完全二叉树。
例如:
若设二叉树的深度为h,除第 h 层外,
其它各层 (1~h-1) 的结点数都达到最大个数(即1~h-1层为一个满二叉树),第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
c、二叉树的存储
-
数组存储
将二叉树补全为满二叉树,然后编号,按照编号依次存入数组中。缺点:浪费巨大的空间。
-
链式存储
使用链表存储
d、二叉树遍历
每个元素访问且只访问一次
-
三种遍历方法
具有很强的递归特性- 先序遍历
”根左右“,先遍历根,然后左边,最后右边 - 中序遍历
“左根右”,先遍历左边,然后根,最后右边 - 后续遍历
“左右根”:先遍历左边,然后右边,最后根
例如:
先序遍历: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右边
-
(2)左右分别重复(1)操作
-
(3)不停的重复(1)操作, 最终的二叉树图为:
-
-
-
已知“中序,后序”,反推画出树图,从而可以求出先序。
f、链表实现二叉树
例如:
#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、图
类型 | 描述 |
---|---|
线性表 | 每个节点最多有一个前驱,一个后继 |
树 | 每个节点最多只有一个前驱,可以有多个后继 |
图 | 可以有多个前驱,可以有多个后继,例如:人际关系网,计算机网络 |
(1)图的存储
a、二维数组/矩阵
缺点:太浪费空间
b、链表存储
记录每个节点相连的其他节点,以及他们的距离
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
总结:
深度遍历和广度遍历都是图遍历算法中常用的方法。深度遍历算法适合用于查找路径以及判断图的连通性等问题,而广度遍历算法适合用于寻找最短路径以及拓扑排序等问题。
例如:(下图答案均不唯一!)
(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)
哈希值,使用哈希函数求,它能将任意输入转换为整数。哈希查找时间一般不随数据量变化,而且是直接给你结果。
例如:(学生管理系统)
#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),效率比其他排序算法要低,所以不推荐在大规模数据排序时使用选择排序。
例如:
#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),不太适用于大规模数据的排序。
例如:
#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再走,否则会出错!
#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)逆序
-
数组的逆序:首尾元素互换
-
链表的逆序
-
单向链表的的逆序:头插法
//链表节点反转(头插法) 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; } }
-
双向链表的的逆序:头插法
-
//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");
}