数据结构(顺序表,链表,栈,队列的实现)

目录

1. 顺序表

1.1顺序表的结构

1.2 顺序表的实现

2. 链表

2.1链表的分类

2.1.1关于单向与双向

2.1.2关于循环与非循环

2.1.3关于带头或者不带头

2.2单项不带头非循环链表的实现

2.3双向带头循环链表的实现

2.4顺序表与链表的优缺点

3.栈

3.1 栈的概念与结构

3.2 栈的实现

4. 队列

4.1 队列的结构与概念

4.2队列的实现


1. 顺序表

1.1顺序表的结构

顺序表是一种线性表,是n个具有相同特性的数据元素的有限序列,呈现出一条线性,常见的线性表有,顺序表,链表,栈,队列,字符串,本质上都是数组,但是在数组上区分了静态的与动态的,而且是挨着存放的。顺序表本质上就是一个数组,在数组的基础上,要求是连续存储的,不能跳跃存储。

1.2 顺序表的实现

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDatatype;
//顺序表结构的定义
typedef struct SeqList
{
    SLDatatype* a;
    int size;//顺序表储存的数据数量
    int capacity;//顺序表的实际大小
}SL;

void SLInit(SL* ps);//顺序表的初始化
void SLPushBack(SL* ps, SLDatatype x);  //尾插
void SLPopBack(SL* ps);                 //尾删

void SLPushFront(SL* ps, SLDatatype x); //头插
void SLPopFront(SL* ps);                //头删

void SLPrint(SL* ps);                    //打印
void SLDestory(SL* ps);                 //销毁
void SLCheckCapacity(SL* ps);           //检查容量-扩容

void SLInsert(SL* ps, int pos, SLDatatype x);//任意位置插入
void SLErase(SL* ps, int pos);          //任意位置删除
int SLFind(SL* ps, SLDatatype x);      //查找
void SLModify(SL* ps,int pos, SLDatatype x);    //任意位置修改

顺序表的接口函数实现

#include "SeqList.h"
//初始化
void SLInit(SL* ps)
{
	ps->a= NULL; 
	ps->size = ps->capacity= 0;
}

//检查——扩容
void SLCheckCapacity(SL* ps)
{
	if (ps->size == ps->capacity)
	{
		int newcapacitv = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDatatype* tmp = (SLDatatype*)realloc(ps->a, newcapacitv * sizeof(SLDatatype));
		if (tmp == NULL)
		{
			perror("relloc:");
			exit(-1);//退出程序
		}
		ps->a = tmp;
		ps->capacity = newcapacitv;
	}
}

//尾插
void SLPushBack(SL* ps, SLDatatype x)
{
	
	SLCheckCapacity(ps);//先检查内存
	ps -> a[ps->size] = x;
	ps->size++;
}

//打印
void SLPrint(SL* ps)
{
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}


//销毁
void SLDestory(SL* ps)//
{                     //
	if (ps->a)
	{
		free(ps->a);
		ps->a = NULL;
		ps->size = ps->capacity = 0;
	}
}

//尾删
void SLPopBack(SL* ps)
{
	assert(ps->size);
	ps->size--;
}

//头插
void SLPushFront(SL* ps, SLDatatype x)
{
	SLCheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[0] = x;
	ps ->size++;
}

//头删
void SLPopFront(SL* ps)
{
	assert(ps->size);
	int begin = 1;
	while (begin < ps->size)
	{
		ps->a[begin-1] = ps->a[begin];
		begin++;
	}
	ps->size--;
}
 
//任意位置插入
void SLInsert(SL* ps, int pos, SLDatatype x)
{
	assert(ps);
	//检查ps
	assert(pos >= 0 && pos <=ps->size);
	SLCheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;
	ps->size++;
}

//任意位置删除
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	//检查边界
	/*int begin = pos;
	while (begin < ps->size - 1)
	{
		ps->a[begin] = ps->a[begin + 1];
		begin++;
	}*/
	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		begin++;
	}
	ps->size--;
}

//查找
int SLFind(SL* ps, SLDatatype x)
{
	assert(ps);
	for (int i = 0; i <= ps->size; i++)
	{
		if (ps->a[i] == x)
			return i;
	}
	return -1;
 
}

//修改 
void SLModify(SL* ps, int pos, SLDatatype x)  
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
 
	ps->a[pos] = x;
 
}

在实际使用中,顺序表是存在很大缺陷的,如果空间不够了,就要增容,增容是需要代价的,realloc原地扩容,异地扩容,原地扩容返回的指针是不会变的,假设原地没有足够大的空间,异地扩容会返回另一个大空间的指针。这个异地扩容,不仅要寻找大空间,还要拷贝数据,还要释放旧内存。而且为了避免频繁扩容。我们扩容基本都是扩大二倍,可能会导致一定空间的浪费。而且,顺序表是要求从开始位置连续存储,在中间位置动手,就要频繁挪动数据,所以,我们就需要链表。

2. 链表

链表针对顺序表的缺陷,每一个区域存数值,并且存储下一个区域的地址,以便能够找到下一个区域。

链表实现时的结构

在操作中,我们通常都是直接操控指针了。

2.1链表的分类

链表有8个分类。

2.1.1关于单向与双向

链表中只储存下一个结构体的地址叫做单向,储存了上一个与下一个结构体的地址叫做双向。

2.1.2关于循环与非循环

尾节点链接空指针就是非循环,尾节点的next连接上头节点,我们称之为循环链表。

2.1.3关于带头或者不带头

关于不带头链表,就是一个普普通通的链表,每一个节点都储存数据,和下一个节点的数据。

带头链表呢,就是一个拥有“哨兵位”的链表。这个链表的头节点不储存有效数据,像是在站岗一样,带着下一个节点的地址。这与不带头节点的链表有什么区别呢?

不带头链表的头节点指针要指向第一个数据,如果我要头插,我需要改变这个头节点指针指向的位置,在头部插入一个新的节点,那么头节点就要指向这个新的头,我要改变这个指针指向的方向,所以,我要传参这个指针的地址,也就是二级指针。

但是,如果我带上一个哨兵位,这个位置的结构体不储存任何数据,从而使这个节点永远是我的头节点,致使头节点指针指向的结构体永远不发生改变,所以不需要传二级指针。

我们分别实现一个单项不带头非循环链表和一个双向带头循环链表。

2.2单项不带头非循环链表的实现


#pragma once
#include <stdio.h>
#include <stdlib.h>
typedef int SLDataType;
typedef struct SListNode
{
    SLDataType data;
    struct SListNode* next;
}SLTNode;

SLTNode* BuylistNode(SLDataType x);
void SListPrint(SLTNode* phead);
void SListInit(SLTNode* phead);
void SListPushBack(SLTNode** pphead,SLDataType x);
void SListPopBack(SLTNode** pphead);
void SListPushFront(SLTNode** pphead,SLDataType x);
void SListPopFront(SLTNode** pphead);
SLTNode* SListFind(SLTNode* phead,SLDataType x);
void SListInsert(SLTNode** pphead,int pos,SLDataType x);
void SListEase(SLTNode** pphead,int pos,SLDataType x);
void SListDestory(SLTNode** pphead);
void SListM(SLTNode** pphead,int pos,SLDataType );
#include "SList.h"
//新建链表
SLTNode* BuylistNode(SLDataType x)
{
    SLTNode* newnode =(SLTNode*)malloc(sizeof(SLTNode));
    newnode->data=x;
    newnode->next=NULL;
    return newnode;
}

void SListPrint(SLTNode* phead)
{
    SLTNode* cur=phead;
    while(cur!=NULL)
    {
        printf("%d->",cur->data);
        cur=cur->next;
    }
    printf("NULL");
}

void SListPushBack(SLTNode** pphead,SLDataType x)
{
    SLTNode* newnode= BuylistNode(x);
    if (*pphead==NULL)
    {
        *pphead=newnode;
    }
    else
    {
        SLTNode* tail=*pphead;
        while(tail->next!=NULL)
        {
            tail=tail->next;
        }
        tail->next = newnode;
    }
}

void SListPopBack(SLTNode** pphead)
{
    SLTNode* tailFront=NULL;
    SLTNode* tail=*pphead;
    while(tail->next!=NULL)
    {
        tailFront=tail;
        tail=tail->next;
    }
    free(tail);
    tail=NULL;
    tailFront->next=NULL;
}

void SListPushFront(SLTNode** pphead,SLDataType x)
{
    SLTNode* newnode= BuylistNode(x);
    if(*pphead==NULL)
    {
        *pphead=newnode;
    }
    else
    {
        newnode->next=(*pphead);
        *pphead=newnode;
    }
}

void SListPopFront(SLTNode** pphead)
{
    if(*pphead==NULL)
    {
        return;
    }
    else
    {
        SLTNode* prev=(*pphead)->next;
        free(*pphead);
        *pphead=prev;
    }
}

SLTNode* SListFind(SLTNode* phead,SLDataType x)
{
    SLTNode* cur=phead;
    while(cur)
    {
        if((cur->data)==x) return cur;
        else
        {
            cur=cur->next;
        }
    }
    return NULL;
}

//任意位置插入
void SListInsert(SLTNode** pphead,int pos,SLDataType x)
{
    SLTNode* newcode=BuylistNode(x);
    if(pos-1<=0)
    {
        SListPushFront(&*pphead,x);
    }
    else
    {
        SLTNode* pre=*pphead;
        SLTNode* Front_pre=*pphead;
        for (int i = 0; i < pos; i++)
        {
            Front_pre=pre;
            pre=pre->next;
        }
        Front_pre->next=newcode;
        newcode->next=pre;
    }
}

//任意位置修改
void SListEase(SLTNode** pphead,int pos,SLDataType x)
{
    SLTNode* cur = BuylistNode(x);
    SLTNode* pre = *pphead;
    SLTNode* Front_pre=*pphead;
    for(int i=0;i<pos-1;i++)
    {
        Front_pre= pre;
        pre = pre->next;
    }
    cur->next=pre->next;
    Front_pre->next=cur;
    free(pre);
}

void SListDestory(SLTNode** pphead)
{
    SLTNode* cur=*pphead;
    while(cur)
    {
        SLTNode* next=cur->next;
        free(cur);
        cur=next;
    }
   
}

void SlistDelete(SLTNode** pphead,int pos)
{
    if(*pphead==NULL)
    {
        printf("该链表为空,无法删除");
        exit(-1);
    }
    SLTNode* prev=*pphead;
    SLTNode* Front_prev=*pphead;
    for(int i=0;i<pos-1;i++)
    {
        Front_prev=prev;
        prev=prev->next;
    }
    Front_prev->next=prev->next;
    free(prev);

}

2.3双向带头循环链表的实现

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int LTDataType;
typedef struct  ListNode
{
    LTDataType data;
    struct ListNode* next;
    struct ListNode* prev;
}LTNode;

LTNode* ListInit();
void ListPushBack(LTNode* phead,LTDataType x);
void ListPopBack(LTNode* phead);
void ListPushFront(LTNode* phead,LTDataType x);
void ListPopFront(LTNode* phead);
void ListEarse(LTNode* phead,int pos,LTDataType x);
void ListFind(LTNode* phead,LTDataType x);
void ListInsert(LTNode* phead,int pos,LTDataType x);
#include "List.h"
/*哨兵位不需要数值
也正是因为哨兵位没有具体的数值
不需要再对链表做调整的时候修改
使得plist所指向的位置永远不用发生改变
从而不对phead指针做调整
只调整其后的结构体
因而不用传二级指针*/

LTNode* ListInit()
{
    LTNode* phead=(LTNode*)malloc(sizeof(LTNode));
    phead->next=phead;
    phead->prev=phead;
    return phead;
}

void Listprint(LTNode* phead)
{
    assert(phead);
    LTNode* cur=phead->next;
    while(cur!=phead)
    {
        printf("%d-",cur->data);
        cur=cur->next;
    }
    printf("\n");
    
}

LTNode* Buy_new_list_node(LTDataType x)
{
    LTNode* newnode=(LTNode*)malloc(sizeof(LTNode));
    newnode->data=x;
    return newnode;
}

void ListPushBack(LTNode* phead,LTDataType x)
{
    assert(phead);
    LTNode* tail=phead->prev;
    LTNode* newnode=Buy_new_list_node(x);

    //链接旧的尾部与新的尾部
    tail->next=newnode;
    newnode->prev=tail;
    
    //链接新的尾部与头节点
    newnode->next=phead;
    phead->prev=newnode;
}

void ListPopBack(LTNode* phead)
{
    LTNode* cur=NULL;
    LTNode* Front_cur=NULL;
    cur=phead->prev;
    Front_cur=phead->prev->prev;
    phead->prev=Front_cur;
    Front_cur->next=phead;
    free(cur);
}

void ListPushFront(LTNode* phead,LTDataType x)
{
    assert(phead);
    LTNode* newnode=Buy_new_list_node(x);
    LTNode* cur=NULL;
    cur=phead->next;
    cur->prev=newnode; newnode->next=cur;
    newnode->prev=phead; phead->next=newnode;
}

void ListPopFront(LTNode* phead)
{
    assert(phead);
    LTNode* head=phead->next;
    phead->next=head->next;
    head->next->prev=phead;
    free(head);
}

void ListEarse(LTNode* phead,int pos,LTDataType x)
{
    LTNode* cur=phead->next;
    LTNode* newnode=Buy_new_list_node(x);
    for (int i = 0; i < pos; i++)
    {
        cur=cur->next;
    }
    cur->next->prev=newnode; newnode->next=cur->next;
    cur->prev->next=newnode; newnode->prev=cur->prev;
    free(cur);
}

void ListFind(LTNode* phead,LTDataType x)
{
    assert(phead);
    LTNode* cur=phead->next;
    int i;
    for (i = 1;cur->data!=x; i++)
    {
        if(cur==phead)
        {
            printf("找不到\n");
            break;
        }
        cur=cur->next;
    }
    printf("找到了,在第%d个",i);
}

void ListInsert(LTNode* phead,int pos,LTDataType x)
{
    LTNode* newnode=Buy_new_list_node(x);
    LTNode* cur=phead->next;
    for (int i = 0; i < pos; i++)
    {
        cur=cur->next;
    }
    cur->prev->next=newnode; newnode->prev=cur->prev;
    newnode->next=cur; cur->prev=newnode; 
}

2.4顺序表与链表的优缺点

顺序表与链表

这两个结构各有优势,很难说谁更加优秀。

这是两个相辅相成的结构

顺序表

优点:

  1. 支持随机访问。需要随机访问支持算法可以很好的适用。

  2. cpu高速缓存的利用率更高。

主存之上都是带电储存。

缺点:

  1. 头部中部插入删除时间效率低。

  2. 连续的物理空间,空间不够了要扩容,扩容有一定程度的消耗。而且可能存在空间浪费。

链表

优点:

  1. 任意位置的插入删除效率高。

  2. 按需申请释放空间。

缺点:

  1. 不支持随机访问。(用下标访问)意味着:一些排序,二分查找不适用。

关于顺序表在内存上的优势,具体可以在coolshell.cn/去查看  与程序员有关的CPU缓存知识

3.栈

3.1 栈的概念与结构

栈是一种特殊的线性表,其中允许在固定的一段进行插入和删除元素操作。进行数据插入和删除的操作的一段称为栈顶,另一端称为栈底。栈中的数据元素遵循着后进先出LIFO(Last In Frist Out)的原则。

压栈:站的插入操作叫做进栈/压栈/入栈,入栈元素在栈顶。

出栈,站的删除操作就叫做出栈。出数据也在栈顶。

栈只能在栈一端进入与输出,简单点讲,栈就好像一个圆形的桶,栈中的数据就像是圆盘,进入的事后从最顶上进入,出去的时候也是从最顶上输出。也像弹夹一样。

要想实现栈,可以用两种方式来实现。可以用数组来实现,也可以用链表来实现。

如果要使用链表栈,一般我们推荐要用头作栈顶,因为链表的头插与头删效率都是比在尾部删除插入要高的。并且设计成双向链表,否则删除数据效率低。

那么哪个结构更好呢?

显而易见应该是数组栈稍微好一点。

数组的缺点无非就是比链表对内存的要求更高一点,但除非是在非常需要节省的结构上,比如嵌入式,或者物联网。

在学会顺序表与链表之后,实现栈就变得轻而易举。下面我们来对顺序数组栈进行实现。

3.2 栈的实现

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int STDataType;
typedef struct Stack
{
    STDataType* a;
    int top;
    int capacity;
}ST;

void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps,STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
int StackSize(ST* ps);
bool StackEmpty(ST* ps);
#include "Stack.h"
/*
初始化的时候,top给的是0,意味着top指向栈顶数据的下一个
如果给的是-1,意味着top指向栈顶数据

当top是0的时候,位于数组的最前方,先给值,之后++
top会停在数组栈顶元素的下一个
因为下一步top就要插入值,所以top在栈顶数据的下一个

当top是-1的时候,插入数据时,就会先++top,再插入数据,
此时top指向的就是栈顶数据
因为下一步要++,再插入数据,所以top指向的是栈顶数据
*/

//初始化栈
void StackInit(ST* ps)
{
    assert(ps);
    ps->a=NULL;
    ps->top=0;
    ps->capacity=0;
}

//栈扩容
void StackDilatation(ST* ps)
{
    if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = realloc(ps->a,sizeof(STDataType)*newcapacity);
		if (tmp == NULL)
		{
			perror("relloc fail");
			exit(-1);//退出程序
		}
		ps->a=tmp;
		ps->capacity = newcapacity;
	}
}

//入栈
void StackPush(ST *ps, STDataType x)
{
    assert(ps);
    StackDilatation(ps);
	ps->a[ps->top]=x;
	ps->top++;
}

//出栈
void StackPop(ST* ps)
{
    assert(ps);
	assert(ps->top);
	ps->top--;
}

//打印
void StackPrint(ST* ps)
{
	for (int i = 0; i < ps->top; i++)
	{
		printf("%d-",ps->a[i]);
	}
	printf("\n");
}

//销毁栈
void StackDestroy(ST* ps)
{
	assert(ps);
	if (ps->a)
	{
		free(ps->a);
		ps->a = NULL;
		ps->top = ps->capacity = 0;
	}
}

//站内查找
void StackFind(ST* ps,STDataType x)
{
	int i=0;
	for (i = 0;i< ps->top; i++)
	{
		if(ps->a[i]==x)
		{
			printf("在第%d个位置\n",i+1);
			break;
		}
	}
	printf("没找到\n");
}

//栈顶元素
STDataType StackTop(ST* ps)
{
	assert(ps);
	return ps->a[ps->top-1];
}

//栈的大小
int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

//判断栈是否为空
bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top==0;
}

4. 队列

4.1 队列的结构与概念

队列只允许在一段进行插入数据操作,在另一端惊醒删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)

入队列:进行插入操作的一段称为队尾

出队列:进行删除操作的一段称为队头

4.2队列的实现

队列的实现也可以用数组与链表这两种方式,相较于栈的实现,队列的实现使用链表要更优,因为如果使用队列结构,就要在头上出数据,如果用数组的话,频繁的挪动数据,会使效率非常低下。

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int QDataType;
typedef struct QueueNode
{
    struct QueueNode* next;
    QDataType data;
}QNode;


/*这里将原有的指针换成结构体
能够方便我们找头找尾
而且函数传参也不需要二级指针了*/
typedef struct Queue
{
    QNode* head;
    QNode* tail;
}Queue;

void QueueInit(Queue* pq);
void QueueDestory(Queue* pq);
void QueuePush(Queue* pq,QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);
#include "Queue.h"

void QueueInit(Queue* pq)
{
    assert(pq);
    pq->head=NULL;
    pq->tail=NULL;
}

void QueueDestory(Queue* pq)
{
    assert(pq);
    QNode* cur = pq->head;
    while(cur!=NULL)
    {
        QNode* next=cur->next;
        free(cur);
        cur=next;
    }
    pq->head=pq->tail=NULL;
}

void QueuePush(Queue* pq,QDataType x)
{
    assert(pq);
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    newnode->data=x;
    newnode->next=NULL;

    if(pq->head==NULL)
    {
        pq->head=pq->tail=newnode;
    }
    else
    {
        pq->tail->next=newnode;
        pq->tail=newnode;
    }

}
void QueuePop(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq));
    QNode* myhead=pq->head;
    pq->head=pq->head->next;
    free(myhead);

    /*如果不加这个判断,就会导致当只有一个节点的时候
    原来的head已经释放,但是tail没有
    以后再访问tail的数据时,就会出现tail为野指针的情况*/
    if(pq->head==NULL)
    {
        pq->tail=NULL;
    }
}
QDataType QueueFront(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq));
    return pq->head->data;
}
QDataType QueueBack(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq));
    return pq->tail->data;
}
int QueueSize(Queue* pq)
{
    assert(pq);
    int n=0;
    QNode* cur=pq->head;
    while(cur)
    {
        ++n;
        cur=cur->next;
    }
    return n;
}
bool QueueEmpty(Queue* pq)
{
    assert(pq);
    return pq->head==NULL;
}

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值