数据结构学习笔记(上部)-c语言版

算法描述:
 

  1. 算法的特性:有穷性,可行性,确定性,唯一性,输入,输出;

  2. 判断算法的优劣基本准则:正确,可读,健壮,高效;

  3. 算法效率:

  •  时间复杂度--- 整体代码的执行频度是单行代码执行频度的和;只取整体执行频度的最高次幂
  • 空间复杂度:空间复杂度主要用于描述某个算法对应的程序现想在计算机上执行,除了用来存储代码与输入数据的内存空间外,还需要的额外空间。

线性表--(一种数据结构):

  1. 描述: 线性表是 n 个数据元素的有限序列,其中 n 个数据是相同数据类型(常用于结构体)的;
  2. 使用案例 --图书信息管理系统;

顺序表

区分线性表与顺序表:

顺序表的描述:--(一种线性表的实现方式),其数据存取方式为随机存取;-通常使用数组为基础结构;

//顺序表结构定义:
#define MAXSIZE=100;
typedef int ElemType;//将int类型 重新命名为ElemType //便于统一修改类型;
typedef struct {
    ElemType data[MAXSIZE];
    int length;
}SeqList //使用结构体 定义了一种抽象数据(ADT) 顺序表

顺序表的 初始化:--(数组静态分配 -栈内存分配):

#define MAXSIZE=100
typedef int ElemType 
typedef struct
{
   ElemType data=[MAXSIZE];
   int length;
}SeqList

//初始化顺序表的长度
void initList(SeqList *L)
{
  L->length=0;
}
int main()
{
    SeqList list;//创建一个顺序表
    initList(&list//取地址);
             
    return 0;
}

顺序表的添加,遍历与删除 查找操作

int addElemt(SeqList *L, ElemType e) //返回值设置为int 0/1 代表真假值
{
    if(L->length>MAXSIZE)
    {
        printf("顺序表已经满了");
        return 0;
    }
     L->data[L->length]=e; 
      //把顺序表长度增加:
    L->length++;
    retrun 1;
}
//插入元素
int insertElem(SeqList *L,int pos,ElemType e)
{
    for(int i= L->length-1;i>=pos-1;i--)
    {
        //一次覆盖-向后移
        L->data[i+1]=L->data[i];
    }
    //放入数据;
    L->data[pos-1]=e;
    //增加长度
    L->length++;
    return 1; 
}
//遍历
void listElem(SeqList * L)
{
    for(int i=0;i<L->length;i++)
    { 
        printf("%d",L->data[i]);
    }
    printf("\n");
}
//删除
int deleteElem(SeqList *L,int pos ,ElemType *e){
//仅传入删除位置,可以返回此位置对应的元素 --获取到被删除的元素;
    *e =L-> data[pos-1]; 
    //该删除末尾数据在内存仍然存在 --只是length-1 后不能再获取了
    if(pos<L->length)
    {  //注意i-1 的范围 pos-1 到 length -2 ;
        //i的范围 pos 到 length-1 
        for(int i=pos;i<L->length;i++)
        {
            L->data[i-1]=L->data[i];
        }
    }
    L->length--;
    return 1;
}
//查找:
// 传入顺序表指针 被查找元素
int findElem(SeqList *L,ElemType e)
{
  for(int i=0;i<L->length;i++)
  {
      if(L->data[i]==e)
      {
         return i+1;
      }
  }
 return 0;
}


单向链表:

描述:链表的存取方式是顺序存取,存放空间随使用空间变化,空间动态可变;

单向链表的基本结构:
1. 头节点:headNode,指针域指向首节点,数据与一般赋值为0/null

2.首节点:数据域中存放首个可用数据

2.单个链表结点:单个链表节点包括数据域和指针域两部分;

3. 尾部节点:指针域指向NULL;

代码实现:
 

#include<stdio.h>
#include <stdlib.h>
typedef int ElemType;//将int类型 重新命名为ElemType //便于统一修改类型;
typedef struct node{
    struct node *next;//在结构体中定义指
    ElemType data;
}Node;//每个结构体代表一个结点 Node

//链表的初始化:
//1. 创建头节点;
//2. 让头结点中的结构体指针赋值为0;
//3. 该头结点对应data数据为0;

Node *initList()
{
    //动态分配内存
    Node* head=(Node*)malloc(sizeof(Node));
    head->data = 0;//基本设置为0
    head->next = NULL;
    return head;
}
//链表中元素的插入--头插法
//1.将新建元素 指向头节点的后一元素
//2. 将头节点指向 新增元素
void insertHead(Node* l,ElemType e)
{   
    //首先先创建一个结点
    Node* p = (Node*)malloc(sizeof(Node));
    //放入元素
    p->data = e;
    p->next = l->next;
    l->next = p;
}

//对链表的遍历:
void listNode(Node* l)
{
    Node* p = l->next;
    while (p != NULL)
    {
        printf("%d\n", p->data);
        p = p->next;
    }
    printf("\n");
}

//获取尾部结点:
Node* getTail(Node* L)
{ 
  //直接传址操作--为了不改变原链表指针的操作,应新创建指针
    Node* p = L->next;
    while (p->next != NULL)
    {
        p = p->next;
    }
    return p;
}
//尾插法:
//1. 先获取尾部结点的地址:
//2.参数: 传入尾部结点, 要插入的元素:

Node* insertTail(Node* tail, ElemType e)
{
    //创建结点:
    Node* p = (Node*)malloc(sizeof(Node));
    p->data = e;
    tail->next = p;
    p->next = NULL;
    return p;//返回新的尾部节点?
}

//在任意位置添加元素:
int insertNode(Node* L,int pos, ElemType e)
{
    //遍历获得位置前一元素:
    Node* p = L;
    //需要记住 ---计数器从0 开始 ,小于 ,具体位置
    int i = 0;
    while (i < pos - 1)
    {
        p = p->next;
        i++;
        if (p == NULL)
        {
            return 0;
        }
    }

    //创建新的结点:
    Node* q = (Node*)malloc(sizeof(Node*));
    q->data = e;
    q->next = p->next;
    p->next = q;
    return 1;
}

// 删除结点操作:
 //被删除元素的前一元素指向被删元素的后一元素:
ElemType deleteNode(Node* L, int pos)
{
    Node* p=L;
    int i = 0;
    //获取前一元素
    while (i < pos - 1)
    {
        p = p->next;
        i++;
        //删除位置没有元素
        if (p->next == NULL)
        {
            return 0;
        }
    }

    Node* q = p->next;
    p->next = q->next;
    //p->next = (p->next)->next;  //语法正确
    //p = q;//q 代表被删除元素结点
    //释放 结点:
    free(q); //
    return 1;
}

//获取链表的长度
int getLength(Node* L)
{
    Node* p = L;
    int i = 0;
    while(p->next!=NULL)
    {
        p = p->next;
        i++;
    }
    return i;
}

//释放链表:

int freeList(Node* L) {
    Node* p = L->next;//p代表第一个结点
    //让q指向p的下下个结点
    Node* q = p->next;
    if (p ==NULL)
    {
        return 0;
    }
    //依次循环释放结点
    while (p != NULL)
    {
        q = p->next;
        free(p);
        p = q;
    }
    //让头节点指向空:  //否则头节点将指向位置(已被释放)
    L->next = NULL;

}

双向链表:

描述:在单向链表的基础上添加了一个指针域,用于指向链表节点的前驱节点;

 作用:通过新增指针域,对链表的操作会被简化---由于其特性,双向链表在某些场景下比单向链表和其他数据结构更有效尤其是在需要频繁插入和删除操作时。

代码实现:

// 双向链表:
#include <stdio.h>
#include <stdlib.h>
// 定义数据类型:
typedef int ElemType;
// 定义节点类型:
typedef struct node
{
    ElemType data;
    // 设置前后结点指针:
    struct node* next, * prev;
} Node;

// 头插法实现:
void insertHead(Node* head, ElemType val)
{

    // 新建结点:
    Node* p = (Node*)malloc(sizeof(Node));
    p->data = val;
    p->prev = head;
    // 新建结点的后继结点设置为头节点的下一节点:
    p->next = head->next;
   //如果不是NULL将后一结点的前驱设置为新建节点(注意判断是否存在节点)
    if (head->next != NULL)
    {
        head->next->prev = p;
    }
    head->next = p;
}

//遍历双向结点:

//对链表的遍历:
void listNode(Node* l)
{
    Node* p = l->next;
    while (p != NULL)
    {
        printf("%d\n", p->data);
        p = p->next;
    }
    printf("\n");
}

//初始化双向链表:
Node* initList()
{
    //动态分配内存
    Node* head = (Node*)malloc(sizeof(Node));
    head->data = 0;//通常设置为0
    head->next = NULL;
    head->prev = NULL;
    return head;
}
//删除结点;
void delectNode(Node*head,int pos)
{
    //首先找到被删除结点的前置结点(如果 被删结点为最后一个结点)
    int i = 1;
    Node* p = head;//一开始赋值统一为头节点
    //找到前置结点
    while (i < pos)
    {
        p = p->next;
        i++;
        //确保删除位置存在
        if (p == NULL)
        {
            return 0;
        }
    }
    Node* q = p->next;
    Node* m = p->next->next;
    //对前置节点操作
    if (m == NULL)
    {
        p->next = NULL;
    }
    else {
        p->next = m;
        m->prev = p;
    }
    free(q);
}


单向循环链表:

描述:循环链表是另一种形式的链式存储结构,其特点是表中的最后一个节点的指针域指向头节点,整个链表形成一个环。

注意:

单向链表和循环链表的区别:判别当前指针p是否指向表尾节点的终止条件不同,单链表为p!=NULL或者p->next!=NULL, 而循环链表的判定条件是p!=L或者p->next!=L

链表习题练:

1.快慢指针:

    1.创建快慢指针
    2.快指针先走k步
    3.快慢指针一起走,直到快指针走到NULL,慢指针此时为对应节点

2. 题目:获取相同后缀中的是首元素:

1. 创建快慢节点;
2. 获取两个链表的长度
3. 快指针先走两长度之差步,
4. 快慢指针同时走知道 两个指针指向数据相同时,该节点为相同后缀的首元素
2. 拿空间换时间操作:


1. 创建一个长度为n的数组;
2. 将数组元素都赋值为0;
3. 遍历链表如果链表,如果链表数据值对应的数组元素的绝对值为0将其变为1,如果对应元素为1,将该节点进行删除;

顺序表与链表的比较:

栈与队列:

栈(Stack):


特点:
仅在表尾(限制删除插入操作仅能在一个位置进行的表)进行删除插入操作; 元素先进后出,后进先出;

描述:

对于线性表,表尾端为栈顶,表头端为栈底,不含元素的表为空栈

栈和队列的设计目标是高效的按特定顺序处理数据,遍历并不是它们的主要功能。

元素的添加删除;

  1. pop(删除):出栈;
  2. push(添加):入栈;

代码实现:

顺序表存储(静态内存分配)

typedef int ElemType;
//栈的顺序存储
typedef struct {
	ElemType data[100];
	int top;//顺序存储中top代表数组下标 //栈中代表栈顶指针
}Stack;
//初始化栈结构:
void initStack(Stack* s)
{
	s->top = -1;
	return ;
}
//判断是否为空:
void isEmpty(Stack* s)
{
	if (s->top == -1)
	{
		printf("此栈为空\n");
	}
	else
	{
		printf("此栈不为空\n");

	}
}
//进行压栈操作:
int  Push(Stack* s, ElemType e)
{
	//先判断栈是否满了:
	if (s->top >= 99)
	{
		printf("栈已经满了\n");
			return 0;
	}
	s->top++;
	s->data[s->top] = e;
	return 1;
}

//进行出栈操作:
int  Pop(Stack* s, ElemType* e)
{
 
	if (s->top == -1)
	{
		printf("此栈为空\n");
		return 0;
	}
	else
	{
		*e = s->data[s->top];
		s->top--;
		return 1;
	}

}

顺序表存储 动态内存分配:(动态开辟空间--指针调用数组)

typedef int ElemType;
//栈的顺序存储
typedef struct {
	ElemType *data;//用于指向堆内存中开辟的数组空间
	int top;//顺序存储中top代表数组下标 //栈中代表栈顶指针
}Stack;

//初始化栈:
Stack* initStack(){
Stack *s=(Stack*)malloc(sizeof(Stack));
    s->data=(ElemType*)malloc(sizeof(ElemType)*100);
    s->top=-1;
    return s;
}

int main()
{
    Stack* s=initStack();
    //之后对栈操作 --传地址;--s报错地址
//其余操作与静态分配相同
}

栈的链式存储结构:
需要注意的点:(栈顶与栈底自定义)
1. 头节点的下一节点--首节点--代表栈顶--添加元素--链表的头插法:

2.栈的长度没有局限

3.判断栈为空 L->next=NULL

typedef int ElemType;
//栈的链式存储:
typedef struct stack
{
  ElemType data;  
  struct stack * next;
}Stack
//初始化栈--(初始化一个链表-创建头节点):
Stack* initStack()
{
Stack *s=(Stack*)malloc(sizeof(Stack));
    s->data=0;
    s->next=NULL;
    return s;
}
//压栈操作--链表的头插法:
void Push(Stack* s,ElemType e)
{   
    Stack*p =s;//p--随函数自动销毁?可以不使用
   //创建新的结点:
    Stack *a=(Stack*)malloc(sizeof(Stack)); //何时
    a->next=p->next;
    p->next=a;
    p->data=e;
    return ;
} 
//判断是否为空:
int isEmpty(Stack * s)
{
  if(s->next==NULL)
  {
      printf("栈为空");
          return 0;
  }
    else return 1;
}
//出栈操作

void Pop(Stack* s,ElemType *e)
{
    //首先判断是否为空
    if(!isEmpty(s))	
    {	
        return 0;
    }
    else
    {
        *e=s->next->data;
        s->next=s->next->next;//
    //正确:
        Stack *p =s->next;
        s->next=p->next;
        free(p);
        return 1;
    }
}

队列:
 

特点:

先进先出的线性表,只允许在一端进行删除,在另一端进行添加元素;
 

结构 描述:

    

队列的顺序存储结构:(静态内存分配):

typedef int ElemType;
typedef struct queue
  ElemType data[100];  
  int front;
  int rear;
}Queue;

//初始化队列:
int  initQueue(Queue* q){
//自定义为 0或 -1 
q—>front=0;
q->queue=0;
return 1;
}
//入队操作:
int  equequ(Queue* q,ElemType e)
{
    //判断是否队列伪满 并将队列重新分配
    if(q->rear>=100)
    {
        if(!queueFull(q))
        {
            return 0;
        }
    }
  q—>data[q->rear]=e;
  q->rear+=1;
    return 1;
}
//判断队列是否为真满:
 int queueFull(Queue *q)
{
 if(q->front==0)
 {
   printf("该队列真的满了");
   return 0;
 }
//设置step 
     int step =Q->front;//->i-step为对应填入的索引
     //注意注意 可以相等 
    for(int i=q->front;i<=q->rear;i++)
    {
     q->data[i-step]=q->data[i]  ;
//i用于取出每个元素
    }
       q->front=0;
     q->rear=q->rear-step;
return 1;
}

//出队操作:
int  oqueue(Queue* q,ElemType* e)
{
   //判断队列是否为空:
    if(q->front==q->rear)
    {
        printf("队列已满");
        return 0;
    }
    q->front--;
    return 1;
}

循环对列:

描述 :

为了解决元素入队时,发生“假溢出”的现象,将队列设置为环状队列;

特点:

  • 在 顺序结构的循环队列中 会留下一个位置给尾指针/队尾指针,数组不会装满;
  • 队列满了与空时的判断:

  队满:(Q->rear+1)%MAXSIZE == Q->front

  队空:Q->rear=Q->front;

  •     队头(front)索引的 增加

q->front=(q->front+1)%MAXSIZE

  •     对尾(rear)索引的赋值

q->rear=(q->rear+1)%MAXSIZE;

实现:
 

  1. 队列的初始化相同:
    rear 和 front 初始化为 0
  2. 队列的入队操作:

//队列满了的判断条件:
int  equeue(Queue * q,ElemType e)
{
    if((Q->rear+1)%MAXSIZE == Q->front)
    {
    printf("满了")
    }   
   //元素入队:
    q—>data[q->rear]=e;
    q->rear=(q->rear+1)%MAXSIZE;
    return 1;
}

出队操作:

int oqueue(Queue *q,ElemType *e)
{
    //判断是否为空
    if(q->rear=q->front)
    {
        prntf("空的\n");
        return 0;
    }
    *e=q->data[q->front];
    q->front=(q->front+1)%MAXSIZE
    //(两个下标前进的条件相同)
        return 1;
}

想象结构:


 

例题:

此案例中 front=0 rear 为 n-1 结合上图看就是 7/(8-1) 的位置,

进行入队操作时,不能先进行判满操作,先进行 rear+1 操作 ,任然会发生数组一个空位置的情况

队列的链式结构:

将头结点的下一结点作为队头;

将尾结点作为队尾 ;

入队则使用尾插法;

typedef struct Node 
{
ElemType data;
struct Node* next;     
}QueueNode 
//新增类型//头尾指针:
typedef struct 
{
  QueueNode * front;
  QueueNode * rear;
}Queue


Queue* initQueue()
{
    //创建首尾指针
    Queue *q =(Queue*) malloc(sizeof(Queue));
    //创建头节点
    QueueNode * node=(QueueNode*)malloc(sizeof(QueueNode));

    q->front=node
    q->rear= node 
        return q //返回首尾指针
}

void eQueue(Queue* q,ElemType e)
{
  //创建新的结点:
    QueueNode * p=(QueueNode*)malloc(sizeof(QueueNode));
   p->data=e;
   p->next=NULL; 
    //尾指针先链接后移动
   q->rear->next=p;
   q->rear=p;
}
int dqueue(Queue * q,ElemType *e)
{
    // 获取被出队结点
 QueueNode *node =q->front ->next;
 *e=node->data;
    //断开操作
 q->front->next=node->next;
    //如果此时是最后一个结点
    if(q->rear==node)
    {
        q->rear=q->front;
    }
    //释放创建的node结点;
    
    free(node);
    return 1;
}

双端队列:(概念多)

描述: 两端都能出 ,两端都能进的队列

解题方法: 假设一端为出队端,画图尝试;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值