数据结构笔记

本文详细介绍了数据结构中的线性结构,包括线性存储和离散存储。线性存储如数组,讲解了如何通过指针分配内存;离散存储以链表为例,讨论了链表的定义、分类、节点表示以及插入和删除操作。此外,文章还探讨了线性结构的应用,如栈和队列的实现与操作。
摘要由CSDN通过智能技术生成

数构

概述

数据结构研究数据的存储问题(个体的存储 + 个体和个体之间关系的存储)

算法研究数据的操作问题

算法依附于存储数据的方式(不同的存储方式对它执行的操作也不一样)

衡量算法的标准

​ 时间复杂度:核心代码执行的次数

数据存储就分两种结构

​ 线性存储

​ 非线性存储

线性结构

线性存储

概述

把所有节点(类似于数组元素,单独的事物、个体)用一根直线穿起来

结构体变量不可以加减乘除 但可以相互赋值

无论一个变量有多大,它的地址只用第一个字节的地址表示(占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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值