数据结构学习笔记

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.速度慢占用存储空间大
循环:不易理解,速度快,存储空间小

线性结构:
每个节点只有一个前驱节点,每个节点只有一个后续节点
首节点没有前驱节点  尾节点没有后续节点

相关推荐