数构
概述
数据结构研究数据的存储问题(个体的存储 + 个体和个体之间关系的存储)
算法研究数据的操作问题
算法依附于存储数据的方式(不同的存储方式对它执行的操作也不一样)
衡量算法的标准
时间复杂度:核心代码执行的次数
数据存储就分两种结构
线性存储
非线性存储
线性结构
线性存储
概述
把所有节点(类似于数组元素,单独的事物、个体)用一根直线穿起来
结构体变量不可以加减乘除 但可以相互赋值
无论一个变量有多大,它的地址只用第一个字节的地址表示(占4个字节)
数组
pArr -> pBase //表示pArr这个结构体变量所指向的结构体中的pBase这个成员
pArr -> pBase = (int *)malloc(seizeof(int) * length)//分配了多个字节,把第一个字节的地址分配给pBase,pBase是整型 正好指向前4个字节,pBase指向第一个元素 pBase+1指向第二个元素
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
struct Arr{
int * pBase;
int len;
int cnt;
};
void init_arr(struct Arr * array,int length);
bool append_arr(struct Arr * array,int value);
bool insert_arr(struct Arr * array,int pos,int value);
bool delete_arr(struct Arr * array,int pos,int * devalue);
bool is_full(struct Arr * array);
bool is_empty(struct Arr * array);
void sort_arr(struct Arr * array);
void show_arr(struct Arr * array);
void inversion(struct Arr * array);
int main(void){
int devalue;
struct Arr arr;
init_arr(&arr,10);
append_arr(&arr,1);
append_arr(&arr,2);
append_arr(&arr,3);
append_arr(&arr,4);
show_arr(&arr);
insert_arr(&arr,1,123);
show_arr(&arr);
delete_arr(&arr,2,&devalue);
show_arr(&arr);
inversion(&arr);
show_arr(&arr);
sort_arr(&arr);
show_arr(&arr);
}
void init_arr(struct Arr * array,int length){//初始化
array->pBase = (int *)malloc(sizeof(int) * length);
array->len = length;
array->cnt = 0;
}
bool append_arr(struct Arr * array,int value){//在数组末尾添加元素
if (is_full(array))//先判断数组是否满了
return false;
else{
array->pBase[array->cnt] = value;
array->cnt ++;//数组有效长度需加1
printf("successful append\n");
return true;
}
}
bool insert_arr(struct Arr * array,int pos,int value){//在数组红任意位置的后面插入元素,一次only插入一个
if ( is_full(array) )//先判断数组是否满了
return false;
while (pos>0 & pos<=array->cnt){//限定元素插入的位置
for (int i=array->cnt-1;i>pos-1;i--){//插入之前先进行插入位置之后的元素集体后移一位
array->pBase[i+1] = array->pBase[i];
}
array->pBase[pos] = value;//插入元素赋值
array->cnt ++;//数组有效值+1
return true;
}
}
bool delete_arr(struct Arr * array,int pos,int * devalue){//删除数组中任意位置的元素
if ( is_empty(array) )//判断数组是否为空
return false;
if (pos<1 || pos>array->cnt)//限制删除数组元素的位置
return false;
*devalue = array->pBase[pos-1];//摘出删除的元素
for (int i=pos-1;i<array->cnt-1;++i){//删除元素后面的数组元素集体前移一位
array->pBase[i] = array->pBase[i+1];
}
array->pBase[array->cnt-1] = 0;//最后一位赋值0,可忽略,因为输出数组内容时在cnt截至
array->cnt --;//数组有效长度-1
printf("successful delete\nthe number you delete is %d\n",*devalue);
return true;
}
bool is_full(struct Arr * array){//判断数组是否满了
if (array->cnt == array->len)
return true;
else
return false;
}
bool is_empty(struct Arr * array){//判断数组是否为空
if (array->cnt == 0)
return true;
else
return false;
}
void sort_arr(struct Arr * array){//失败,后补
int t;
for(int i=0;i<array->cnt-1;++i){
if (array->pBase[i] > array->pBase[i+1]){
t = array->pBase[i];
array->pBase[i] = array->pBase[i+1];
array->pBase[i+1] = t;
}
}
}
void show_arr(struct Arr * array){//输出数组内元素
if (is_empty(array))//先判断数组是否为空
exit(-1);
else
for (int i=0;i<array->cnt;++i){
printf("%d ", array->pBase[i]);
}
printf("\n");
}
void inversion(struct Arr * array){//进行数组颠覆操作
int t,i=0,j=array->cnt-1;
while (i<j){//i<j 保证无论数组内元素个数是基数还是偶数皆可
t = array->pBase[i];
array->pBase[i] = array->pBase[j];
array->pBase[j] = t;
++i;
--j;
}
}
typedef
为已有的数据类型起个名字,两个等价 都可以用
typedef int zhan;//为int多取个名字,int等价于zhan
zhan i;//等价于int i
typedef struct student{
int i;
string name[100];
}ST;
struct student aa;
ST aa;
typedef struct student{
int i;
string name[100];
} * PST,ST;//PST等价于 struct student * 类型,s到*整体是个类型,ST等价于struct student
离散存储
离散 不连续的意思(某个点到某个点之间的间距是可以被计算出来的,若连续则无数)
链表
定义
n个节点离散分配
彼此通过指针相连
每个节点只有一个前驱节点,每个节点只有一个后续节点
首节点没有前驱节点,尾节点没有后续节点
专业术语:
首节点:第一个有效节点(存放有效数据的节点)
尾节点:最后一个有效节点
头节点:
头节点数据类型和首节点类型一样
第一个有效节点之前的那个节点
头节点不存放有效数据
加头节点的目的为方便对链表的操作
头指针:指向头节点的指针变量
尾指针:指向尾节点的指针变量
通过函数处理链表 接受的参数
首节点可以通过头节点找到,尾节点也可
只需要头指针即可
通过头指针可以推算出列表的其他所有信息
表示链表节点的数据类型
结构体变量中的成员指向和该结构体变量数据类型一样的变量
整体分两部分:数据域和 指针域(指向和它本身数据类型一样但是是另外一个节点)
typedef struct Node{
int date;//数据域
struct struct Node * pNext;//指针域
} Node,*PNode;
链表的分类
单链表
双链表:
每一个节点有两个指针域
左边指针域指向前面,右边指针域指向后面
循环链表:
能通过任何一个节点找到其他所有节点
最后一个节点的指针域又指向前面的
非循环链表
非单循环链表插入和删除节点
有一个链表,一个指针p指向某个节点(前后可有),q指向另外 i 一个节点
现需把q指向的节点插入到p指向的节点后面
r=p->pNext;//先把p指向的节点后面的节点的地址保存
p->pNext=q;//先使p指向的节点的指针域指向q
q->pNext=r;//再使q指向的节点的指针域指向p指向的节点后面的节点
q->pNext=p->pNext;//先使q指向的节点的指针域指向p指向的节点的后面的节点
p->pNext=q;//再使p指向的节点指向q
r=p->pNext;
p->pNext=p->pNext->pNext;
free(r);//删除r指向节点所占的内存 手动释放,不会导致内存泄露(only C and C++)
链表的操作
算法:
狭义的算法和数据的存储方式密切相关(狭义,从内部的真正实现上,本质)
广义的算法和数据的存储方式无关 (外表)
泛型:(一种假象,内部已经编好了)
利用某种技术使得:不同的存储方式,执行的操作一样
可以在一个函数分配内存,在另外一个函数去使用内存(跨函数调用内存问题)(only动态内存,函数调用完后静态的内存没了,动态的还有)
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
typedef struct Node{
int date;
struct Node * pNext;
}NODE,* PNODE;
PNODE create_list();
void traverse_list(PNODE pHead);
bool is_empty(PNODE pHead);
int length_list(PNODE pHead);
void sort_list(PNODE pHead);
bool insert_list(PNODE pHead,int pos,int val);
bool delete_list(PNODE pHead,int pos);
int main(void){
PNODE pHead = create_list();
traverse_list(pHead);
sort_list(pHead);
traverse_list(pHead);
insert_list(pHead,3,20);
traverse_list(pHead);
delete_list(pHead,3);
traverse_list(pHead);
return 0;
}
PNODE create_list(){//返回头指针,创建头指针,指向链表尾节点的指针pTail,指向新节点的指针,通过pTail->pNext = PNew 创建链表
int len,value;
PNODE pHead = (PNODE)malloc(sizeof(NODE));//动态内存分配创建头指针
if (pHead == NULL){//确保动态内存分配成功
printf("fail");
exit(-1);
}
PNODE pTail = pHead;//该指针指向链表最后一个节点
pTail->pNext = NULL;
printf("number is ");
scanf("%d",&len);
//PNODE pNew = (PNODE)malloc(sizeof(NODE));//若在循环外面进行动态内存分配pNew指针,则导致致循环结束始终只有一个pNew
for (int i=0;i<len;++i){
printf("number %d =",i+1);
scanf("%d",&value);
PNODE pNew = (PNODE)malloc(sizeof(NODE));//该指针指向新的节点
if (pNew == NULL){//确保动态内存分配成
printf("fail");
exit(-1);
}
pNew->date = value;
pTail->pNext = pNew;
pTail = pNew;
pTail->pNext = NULL;
}
return pHead;
}
void traverse_list(PNODE pHead){//创建一个始终指向链表尾节点的指针
PNODE p = pHead->pNext;
while(p != NULL){
printf("%d ",p->date);
p = p->pNext;
}
printf("\n");
}
bool is_empty(PNODE pHead){
if(pHead->pNext = NULL)
return true;
else
return false;
}
int length_list(PNODE pHead){//与travel函数实现类似
int len=0;
PNODE p = pHead->pNext;
while(p != NULL){
++len;
p = p->pNext;
}
return len;
}
void sort_list(PNODE pHead){
/*
数组中的排序
for(i=0;i<len-1;++i){//把数组中的第一个元素和其后面所有元素依次进行比较,再拿第二个与后面依次进行比较,随之结束
for(j=i+1;j<len;++j){//j变,i不变
if(a[i]<a[j]){
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
{//链表和数组比,链表不连续,但都属于线性结构,内部不一样,但从逻辑上讲他们的算法一样
*/
int i,j,t;
PNODE ip,jp;
int len = length_list(pHead);
for(i=0,ip=pHead->pNext;i<len-1;++i,ip=ip->pNext){
for(j=i+1,jp=ip->pNext;j<len;++j,jp=jp->pNext){
if(ip->date > jp->date){
t = ip->date;
ip->date = jp->date;
jp->date = t;
}
}
}
}
bool insert_list(PNODE pHead,int pos,int val){//在pos位置的后面插入值为val的元素
int i=0;
PNODE p = pHead;
while(p!=NULL && i<pos){//确保pos的值不大于链表的有效节点数,且其余正常时使得i=pos,并且确保pos值的正常
p = p->pNext;
++i;//使得p指向的有效节点为第i个有效节点
}
if (i>pos || p==NULL)//pos值大于链表有效节点数or pos值的正常性
return false;
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (pNew == NULL){
printf("fail");
exit(-1);
}
pNew->date = val;
pNew->pNext = p->pNext;//先让pos节点的后面的节点前面为要插入的节点
p->pNext = pNew;
/*先让pos节点的后面为要插入的节点
PNODE t = p->pNext;
p->pNext = pNew;
pNew->pNext = t;
*/
return true;
}
bool delete_list(PNODE pHead,int pos){//与insert函数方法类似
int i=0;
PNODE p = pHead;
while(p!=NULL && i<pos-1){
++i;
p = p->pNext;
}
if(p==NULL || i>pos-1)
return false;
PNODE t = p->pNext;
p->pNext = p->pNext->pNext;
free(t);
t = NULL;
return true;
}
线性结构的应用
栈
定义:
一种可以实现先进后出的存储结构
类似于箱子
先放进去的最后取出来
分类:
静态栈
动态栈
内存可以分为静态内存(栈内分配,操作系统分配,压栈方式)和动态内存(堆内分配,手动分配,排序方式)
栈and堆表示分配数据的方式
算法:
出栈
入栈
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
typedef struct node{//链表节点的模型
int date;
struct node * pnext;
}node,* pnode;
typedef struct stack{//栈的模型
pnode ptop;
pnode pbottom;
}stack,* pstack;
void init(pstack ps);//使生成一个空栈
void push(pstack ps,int value);
bool is_empty(pstack ps);
bool pop(pstack ps,int *value);
bool clear(pstack ps);
void traverse(pstack ps);
int main(){
stack op;//创建一个栈(其中值为垃圾值)
int delete_value;//删除的元素的值
init(&op);
push(&op,2);
push(&op,4);
push(&op,5);
traverse(&op);
pop(&op,&delete_value);
traverse(&op);
clear(&op);
traverse(&op);
}
void init (pstack ps){//对栈进行初始化,动态分配一个链表节点(头指针),使ptop和pbottom指向它,使栈中存放的不是垃圾值
ps->pbottom = (pnode)malloc(sizeof(node));//因为是动态内存分配的,所以该函数调用完后该内存不会被自动释放
if(ps->ptop == NULL){
printf("fail");
exit(-1);
}
ps->ptop = ps->pbottom;
ps->ptop->pnext = NULL;//头指针内不存放有效数据
}
void push(pstack ps,int value){//压栈,动态分配一个链表节点pnew,pnew指向ptop指向的,ptop再指向pnew
pnode pnew = (pnode)malloc(sizeof(node));
pnew->date = value;
pnew->pnext = ps->ptop;
ps->ptop = pnew;
}
bool is_empty(pstack ps){
if(ps->ptop == ps->pbottom)
return true;
else
return false;
}
bool pop(pstack ps,int *value){//出栈,先把栈顶节点保存至r,使ptop指向r->pnext,free(r),如此防止内存泄漏
if(is_empty(ps)){//先判断该栈是否为空栈
printf("fail\n");
return false;
}
else{
pnode r = ps->ptop;
*value = r->date;
ps->ptop = r->pnext;//若直接让ptop指向ptop下面的节点,将使得找不到出栈的节点,造成内存泄漏
free(r);
r = NULL;
return true;
}
}
bool clear(pstack ps){//对栈进行清空,双指针r t,循环(t始终在r下面,free(r),r=t)
if(is_empty(ps)){
printf("fail");
return false;
}
else{
pnode r = ps->ptop;
while(r != ps->pbottom){
pnode t = r->pnext;
free(r);
r = NULL;
r = t;
}
ps->ptop = ps->pbottom;
return true;
}
}
void traverse(pstack ps){//遍历栈,引入一个指针r,r由ptop遍历至pbottom
if(is_empty(ps))
printf("empty!!\n");
else{
pnode r = ps->ptop;
while(r != ps->pbottom){
printf("%d ",r->date);
r = r->pnext;
}
printf("\n");
}
}
队列
定义:一种可以实现 先进先出 的存储结构(先放进去的先出来 )类似于排队去买票
头出(f) 尾增®
只允许一端插入元素
只允许另一端把元素删除
(栈只允许在一端进行插入删除操作)
分类
链式队列:用链表实现
内部是链表,对链表进行的操作和限制 使其变成队列
静态队列:用数组实现(把数组的一些功能砍掉)
静态队列必须是 循环队列
循环队列的几个问题
1.静态队列为什么必须是循环队列
按照一般数组的算法来操作时
f端删除一个元素后永远无法往上移
删除,增加,f r都是往上移(只能加) 如此会造成空间浪费
r永远指向的是当前队列的下一个位置
如若变成循环的,如此加到末尾再加就能到头部去
所以必须是循环队列
2.循环队列需要几个参数来确定,及参数的含义
需要2个参数来确定
且2个参数不同场合下有不同的含义
front rear
队列初始化
front 和 rear 都是0
队列非空
front代表队列第一个元素
rear代表队列最后一个有效元素的下一个元素
队列空
front和rear值相等,但不一定为0
出队,入队(压栈,出栈)
front(出队,删)
rear(入队,头指针,增)
3.入队、出队的伪算法
将值存入 r 代表的位置
r = (r+1) % 数组的长度//(n-1)%n == n-1 , 此为循环队列 防止溢出
f = (f+1) % 数组的长度
4.判断队列是否为空、判断队列是否已满
f可大于r f可小于r f可等于r
f == r
两种方式
多增加一个标识参数(有效长度)
少用一个元素(常用)
if((r+1)%r == f)
满
else
不满3
队列的应用
所有和时间有关的操作都有队列的影子
#include<stdio.h>
#include<malloc.h>
typedef struct Queue{
int *pBase;
int front;
int rear;
} QUEUE;
void init_queue(QUEUE *pQ);
bool full_queue(QUEUE *pQ);
bool en_queue(QUEUE *pQ, int val);
int traverse_queue(QUEUE *pQ);
bool out_queue(QUEUE *pQ, int *pVal);
bool empty_queue(QUEUE *pQ);
int main(){
// QUEUE Q;
// int delVal;
// init_queue(&Q);
// en_queue(&Q, 1);
// en_queue(&Q, 2);
// en_queue(&Q, 3);
// en_queue(&Q, 4);
// traverse_queue(&Q);
// out_queue(&Q, &delVal);
// printf("\n删除的元素为 %d\n", delVal);
// traverse_queue(&Q);
}
void init_queue(QUEUE *pQ){
pQ->pBase = (int *)malloc(sizeof(int) * 6);
pQ->front = pQ->rear = 0;
}
bool full_queue(QUEUE *pQ){
if((pQ->rear+1)%6 == pQ->front)
return true;
else
return false;
}
bool en_queue(QUEUE *pQ, int val){
if(full_queue(pQ))
return false;
else{
pQ->pBase[pQ->rear] = val;
pQ->rear = (pQ->rear + 1) % 6;
return true;
}
}
int traverse_queue(QUEUE *pQ){//创建一个r 使其遍历
int r = pQ->front;
while(r != pQ->rear){
printf("%d ", pQ->pBase[r]);
r = (r + 1) % 6;
}
}
bool out_queue(QUEUE *pQ, int *pVal){
if(empty_queue(pQ))
return false;
else{
*pVal = pQ->pBase[pQ->front];
pQ->front = (pQ->front + 1) % 6;
return true;
}
}
bool empty_queue(QUEUE *pQ){
if(pQ->rear == pQ->front)
return true;
else
return false;
}