数据结构学习笔记
1.衡量算法的标准:
时间复杂度:大概程序执行的次数,而非执行的时间
空间复杂度:算法执行过程中大概所占用的最大内存
难易程度
健壮性
2.int *p //p是个指针变量,int *表示该P变量只能存储int类型变量的地址
3.地址:内存单元的编号,内存是可以被cpu直接访问的,内存的编号是不能重复的,内存的基本划分单位是字节
CPU--地址线(可以确定对哪个地址进行操作)
控制线(控制读和写)
数据线(数据传输)
4.指针就是地址,地址就是指针。
5.指针变量就是存放内存单元地址的变量
6.指针的本质就是一个受限的非负整数
分类:
1.基本类型的指针
int * p//p是个指针变量,int *表示该p变量只能存储int类型的指针变量地址
int *p;
int i =10;
p=&i;
如果*p就是i,可以互换。任何一个改变都会变化
p保存i的地址,修改p的值不改变i的值
*p指向一个不确定的单元的值
2.指针与数组的关系
int a[5]={0,1,2,3,4,5};
printf("%p\n",a+1); 0012FF70 //指向第二个元素
printf("%p\n",a+2); 0012FF74 //指向第三个元素
printf("%d\n",*a+3); 4 //等价于a[0]+3
一维数组名是个指针变量
它存放的是以维数组第一个元素的地址,他的值不能被改变,一唯数组名指向的是数组的第一个元素
a-->a[0]
所以下标和指针的关系:a[i]==*(a+i)
只要是数组物理空间一定是连续的
指针变量的运算:
指针变量不能相加,不能相乘相除
如果两个指针属于同一个数组,就可以相减
p+i的值是p+i*(p所指向的变量所占的字节数)
p-i的值是p-i*(p所指向的变量所占的字节数)
p++==p+1
p--=p-1
int 4个字节 一个字节是8位,一个字节是一个地址
double 8个字节
%p实际就是以十六进制输出
所有的指针变量只占4个子节 用第一个字节的地址表示整个变量的地址
例1:
int main(void){ int i =10; f(&i); printf("i=%d\n",i); //i=99 return 0; } void f(int * p){ *p=99; }
例2:
int main(void){ int i =9; int * p=&i; printf("%p\n",p);//p的地址已经改变 f(&p);//要改变他在内存的值就把地址放进去 printf("%p\n",p); return 0; } void f(int ** q){ *q=(int *)0xFFFFFFFF }
结构体:
1.为什么会出现结构体
#include<stdio.h> #include<string.h> struct Student{ int sid; String name; int age;//只有属性没有方法,功能相对较弱,类更能完整的表现失误,结构体就是类的一个过渡 }
为了表现一些复杂的数据,而普通的基本数据类型无法满足要求。所以会出现
2.什么叫结构体
结构体是用户根据用户实际需要和自己定义的复合数据类型
3.如何使用结构体
int main(void){ struct Student st={1000,"zhangsan",20}; printf("%d %s %d \n",st.sid,st.name,st.age);//字符串输出是%s st.sid=99; strcpy(st.name,"lisi");//字符串要这样赋值 st.age=22; printf("%d %s %d \n",st.sid,st.name,st.age); return 0; } int main(void){ struct Student st={1000,"zhangsan",20}; struct Student * pst; pst=&st; pst->sid=99;//pst->sid等价于(*pst).sid 第一种方式:st.sid 2.ppst->sid return 0; }
4.注意事项
结构体变量不能加减乘除,但是可以相互复制
普通结构体变量和结构体指针变量作为函数传参的问题
//把第一个字节转化成整形,就可以把pArr当成数组使用
int * pArr=(int *)malloc(sizeof(int)* len)
*pArr=4; //类似于a[0]=4
pArr[1]=10;//类似于a[1]=10
free(pArr)//释放内存
下程序中能够调用函数fun(),使main函数中的指针变量p指向一个合法的整形单元:
跨函数使用内存的例子
main(){ int *p; fun(&p); ... } int fun(int **q){ *q=(int *)malloc(4); }
数据的存储不一样,数据的操作就不一样了
数据结构=个体的存储+个体的关系存储
算法=对存储数据的操作
数据结构:
1.数据的存储:
线性结构:把所有的节点用一根直线穿起来
连续存储【数组】:类型大小相同。
数组的优缺点:
离散存储【链表】
链表的优缺点:
线性结构最常见的应用:1.栈,队列
非线性结构存储
2.用c实现java中ArrayList功能
#include<stdio.h> #include<malloc.h> #include<stdlib.h> //定义了一个数据类型,struct Arr的类型 struct Arr{ int *pBase;//存储的是数组第一个元素的地址 int len;//数组所能容纳的最大元素的个数 int cnt;//当前数组有效元素的个数 } void init_arr(struct Arr * pArr,int length); bool append_arr(struct Arr * pArr,int val);//追加 bool insert_arr(); bool delete_arr(); int get(); bool is_empty(struct Arr * pArr); bool is_full(struct Arr * pArr); void sort_arr(); void show_arr(struct Arr * pArr); void inversion_arr(); int main(void){ struct Arr arr; int_arr(&arr,6); show_arr(&arr); append_arr(&arr,1); return 0; } void int_arr(struct Arr * pArr,int length){ pArr->pBase=(int *)maclloc(sizeof(int)* length); if(NULL==pArr->pBase){ printf("动态内存分配失败") exit(-1);//终止整个程序 }else{ pArr->len=length; pArr->=0; } return; } bool is_empty(struct Arr * pArr){ if(0==aArr->cnt){ return true; }else return false; } void show_arr(struct Arr * pArr){ if(is_empty(pArr)){//如果数组为空,提示 printf("数组为空"); }else{ for(int i=0;i<pArr->len;==i){ printf("%d",pArr->pBase[i]);//int *类型 } } bool is_full(struct Arr * pArr){ if(pArr->cnt==pArr->len) return true else return false } bool append_arr(struct Arr * pArr,int val){ //满了就返回false if(is_full(pArr)) return false; //不满就追加 pArr->pBase[pArr->cnt]=val; (pArr->cnt)++; return true; } }
typedef int zhangsan; //zhangsan等价于int typedef struct Student{ int sid; char name[100]; char sex; }ST;
//链表节点的数据类型 struct Node{ int data;//数据域 struts Node * pNext;//指针域 }NODE,* pNODE;
链表分类:
单链表
双链表:每一个节点有两个指针域,双向指
循环链表:能通过任何一个节点找到所有的节点
非循环链表
泛型:利用某种技术达到的效果是:不同的存数方式,执行的操作是一样的
算法:狭义的算法是与数据的存数方式密切相关
广义的算法是与数据的存储方式无关
离散存储[链表]
优点:空间没有限制 插入删除元素很快
缺点:存取速度慢
连续存储【数组】:
优点:
存取速度快
缺点:事先必须知道数组的长度、 插入删除元素很慢、空间通常有限制、需要大块的连续内存
栈
定义:一种可以实现“先进后出”的存储结构
栈类似于箱子
应用:函数调用,中断,表达式求值,内存分配,缓冲处理,迷宫
1.栈程序的演示:
#include <stdio.h> #include <malloc.h> #include <stdlib.h> typedef struct Node{ int data; struct Node * pNext; }NODE,* PNODE; typedef struct Stack{ PNODE pTop; PNODE pBottom; }STACK,* pSTACK; void init(pSTACK); void push(pSTACK,1); void push(pSTACK); int main(void){ STACK S;//STACK等价于struct Stack init(&S);//目的是造出一个空栈 push(&S,1); push(&S,2); traverse(&S);//遍历输出 return 0; } void init(pSTACK pS){ pS->pTop=(PNODE)malloc(sizeof(NODE)); if(NULL==pS->pTop){ printf("动态内存分配失败"); exit(-1); } else{ pS->pBottom=ps->pTop; pS->pTop->pNext=NULL; } } void push(pSTACK pS,int val){ PNODE pNew==(PNODE)malloc(sizeof(NODE)); pNew->data=val; pNew->pNext=pS->pTop; pS->pTop=pNew; } void traverse(pSTACK pS){ PNODE p=pS->pTop; while(p!=pS->pBottom){ printf("%d",p->data); p=p->pNext; }printf("\n"); return; }
链表队列--用链表实现
静态队列--用数组实现
静态队列通常都必须是循环队列
队头出,队为入,所以不论新增还是删除front和rear都会增加,所以静态队列必须是循环队列
队列空:font和rear相等就是空的
入队:r=(r+1)%队列的长度
出队:font=(font+1)%队列的长度
判断队列是否满(f和r值紧挨着):(rear+1)%数组长度==f
递归:
一个函数自己直接或间接调用自己
举例:
1.求阶乘(1)n!=n*(n-1)!
#include<stdio.h> int main(void){ int val; int i,mult=1;s printf("请输入一个数字";) printf("val="); for(i=1;i<=val;++i){ mult=mult*1; printf(mult); } }
2.递归求阶乘(2)
long f(long n){ if(1==n) return 1; else return f(n-1)*n } int main(void){ printf("%d\n",f(3)); return 0; }
3.递归求和
long sum(long n){ if(1==n) return 1; else return n+sum(n-1); } int main(void){ printf("%ld\n",sum(100)); return 0; }
函数的调用:
1.当一个函数在运行期间调用另一个函数时候,在运行被调函数之前,系统需要完成3件事情:
将所有的实际参数,返回地址等信息传递给被调函数保存
为被调函数的局部变量分配存储空间
将控制转移到被调函数的入口
2.从被调函数返回主调函数之前,系统也要完成3件事情
保存被调函数的返回结果
释放被调函数所占用的存储空间
依照被调用函数保存的返回地址将控制转移到调用函数
递归必须满足的3个条件:
1.递归必须要有一个明确的终止条件
2.该函数所处理的数据规模必须在递减
3.这个转化必须是可解的
递归和循环式可以转换的
递归:1.易于理解2.速度慢占用存储空间大
循环:不易理解,速度快,存储空间小
线性结构:
每个节点只有一个前驱节点,每个节点只有一个后续节点
首节点没有前驱节点 尾节点没有后续节点