线性表,栈和队列

241 阅读7分钟

顺序表的优缺点:

优点:1.空间利用率高,数据是连续存放的,无需增加额外的存储空间来表示结点间的逻辑关系。2.存取速度高效,直接通过下标访问。

缺点:1.插入和删除比较慢,在插入或者删除一个元素时,需要遍历整个顺序表进行数据的前后移动。2.需要预先分配足够大的空间,如果估计的过大,可能会导致顺序表后的大量空间被闲置。如果估计的过小,可能会导致溢出。

链表(单链表,双链表,循环链表的插入和删除)及其优缺点:

单链表的插入和删除:

  • 首先初始化单链表,其中主要保存的是该节点的值和下一个节点的地址
有效节点结构体设计:
​
struct Node{
​
int data;//数据域struct Node* next;//指针域
​
}
  • 插入操作(头插,尾插,按位插入)

1.头插法:

bool Insert_head(PNode pn, int val)
{
	assert(pn!=NULL);//判断不为空
 
	//1.申请新节点
	struct Node* pnewnode = (struct Node*)malloc(1 * sizeof(struct Node));
	assert(pnewnode != NULL);
	pnewnode->data = val;
	pnewnode->next = NULL; // 这里可写可不写
 
 
	//2.找到合适的插入
	//头插时插入在头结点的后边,所以不用找
        struct Node* p = pn;
	//3.插入
	pnewnode->next = p->next;//先让C指向B  (不能先断开AB中间的线  不然找不到B)
	p->next = pnewnode;//此时再让A指向C
	return true;
}

2.尾插法

bool Inseret_tail(PNode pn, int val)
{
	//1.申请新节点
	struct Node* pnewnode = (struct Node*)malloc(1 * sizeof(struct Node));
	assert(pnewnode != NULL);
	pnewnode->data = val;
	pnewnode->next = NULL; // 这些步骤都与前面的一样
 
	//2.找到合适的插入位置(尾插,需要一个临时指针p指向尾结点)
	struct Node* p = pn;
	for (p; p->next != NULL; p = p->next);
 
	//3.插入
	pnewnode->next = p->next;
	p->next = pnewnode;
 
	return true;
}

3.按位置插入(将C插入到AB)

bool Inseret_pos(PNode pn, int pos, int val)
{
	
	assert(pos >= 0 && pos <= Get_length(pn));
 
	//1.申请新节点
	struct Node* pnewnode = (struct Node*)malloc(1 * sizeof(struct Node));
	assert(pnewnode != NULL);//确保申请成功
	pnewnode->data = val;//传入val
	pnewnode->next = NULL; // 
 
	//2.找到合适的插入位置(按位置插入AB之间,在这里发现规律,pos等于几则让指向头结点的指针p向后走pos步,此时p指向AB的A)
	struct Node* p = pn;
	for (int i = 0; i < pos; i++)
	{
		p = p->next;
	}
    //3.插入
	pnewnode->next = p->next;//先让C指向B  (不能先断开AB中间的线  不然找不到B)
	p->next = pnewnode;//此时再让A指向C
 
	return true;
}
  • 删除操作

1.头删

bool Del_head(PNode pn)
{
	//assert
	if (IsEmpty(pn))
	{
		return false;
	}
 
	struct Node* p = pn->next;//pn是头结点 pn->next则是第一个有效节点地址  即头删的节点   则此时p指向待删除节点
	//将待删除节点跨越指向(让带删除节点上家的next域指向下家的地址,而下家的地址在待删节点p的next中)
	pn->next = p->next;
	free(p);
	p = NULL;
 
	return true;
}

2.尾删

bool Del_tail(PNode pn)
{
	//assert
	if (IsEmpty(pn))
	{
		return false;
	}
 
	//找到待删除节点用p指向(尾删的待删除节点就是倒数第一个节点)
	struct Node* p = pn;
	for (p; p->next != NULL; p = p->next);//当P指向的next域为空停下来
 
	//q指向倒数第二个节点
	struct Node* q = pn;
	for (q; q->next != p; q = q->next);
 
	q->next = pn;//q->next = p->next;
	free(p);
 
	return true;
 
}

3.按位置删    同样这里改变pos的值能实现头删和尾删

bool Del_pos(PNode pn, int pos)
{
	//assert
	assert(pos >= 0 && pos < Get_length(pn));
	if (IsEmpty(pn))//注意这里的判空操作在后面有定义
	{
		return false;
	}
 
	//先找到待删除节点的上一个节点,用q指向  规律:pos等于几 q就向后走几步
	struct Node* q = pn;
	for (int i = 0; i < pos; i++)
	{
		q = q->next;
	}
	//此时 q指向待删除节点的上一个节点
	//接下来 再让p指向待删除节点 待删除节点在q的next中
	struct Node* p = q->next;
 
	//跨越指向,再释放
	q->next = p->next;
	free(p);//删除待删除节点
 
	return true;
}
  bool IsEmpty(PNode pn)
{
	return pn->next == NULL;
}

4.按值删

bool Del_val(PNode pn, int val)
{
	//assert
	if (IsEmpty(pn))
	{
		return false;
	}
 
	struct Node* p = Search(pn, val);//判断value是否存在
	if (p == NULL)//search返回的地址为空代表没找到
	{
		return false;
	}
	//此时待删除节点存在,  且用指针p指向
 
	struct Node* q = pn;
	for (q; q->next != p; q = q->next);
	//再申请一个临时指针q  让q指向p的上一个节点
 
 
	q->next = p->next;
	free(p);
	//跨越指向,和释放
	return true;
}
 
//查找
struct Node* Search(PNode pn, int val)
{
	//assert
	for (struct Node* p = pn->next; p != NULL; p = p->next)
	{
		if (p->data == val)
		{
			return p; //找到 则扔出去
		}
	}
 
	return NULL; //没找到,返回NULL地址
}

单链表的优缺点

优点:1没有增容问题,插入一个开辟一个空间。2.任意位置插入删除时间复杂度为O(1)

缺点:不支持随机访问,除了值得存储 ,还将要存储下一个节点的地址。

双链表的插入和删除

  • 初始化结构体
#include <stdio.h>
#include <malloc.h>
 
//初始化结构体,一个是前驱指针,一个后继指针. 
typedef struct DoubleLinkedNode{
	char data;
	struct DoubleLinkedNode *previous;
	struct DoubleLinkedNode *next;
} DLNode, *DLNodePtr;
 
//初始化链表 
DLNodePtr initLinkList(){
	DLNodePtr tempHeader = (DLNodePtr)malloc(sizeof(struct DoubleLinkedNode));//为dlnode申请一个大小为doublelinkednode指针大小的内存空间. 
	tempHeader->data = '\0';
	tempHeader->previous = NULL;
	tempHeader->next = NULL;
	return tempHeader;
}

1.插入

//插入链表函数 
void insertElement(DLNodePtr paraHeader, char paraChar, int paraPosition){
	DLNodePtr p, q, r;
 
	// 第一步,查找位置,将指针p放到表头. 
	p = paraHeader;
	for (int i = 0; i < paraPosition; i ++) {
		p = p->next;
		if (p == NULL) {
			printf("位置%d已经是最后一位了", paraPosition);
			return;
		}
	}
 
	//申请一块新的内存空间. 
	q = (DLNodePtr)malloc(sizeof(struct DoubleLinkedNode));
	q->data = paraChar;
 
	//开始插入新的链表. 
	r = p->next;
	q->next = p->next;
	q->previous = p;
	p->next = q;
	if (r != NULL) {
		r->previous = q;
	}
}

2.删除

//删除链表函数. 
void deleteElement(DLNodePtr paraHeader, char paraChar){
	DLNodePtr p, q, r;
	p = paraHeader;
 
	//锁定目标,必须是删除链表的前一个结点. 
	while ((p->next != NULL) && (p->next->data != paraChar)){
		p = p->next;
	}
 
	//报错检查 
	if (p->next == NULL) {
		printf("'%c'字符不存在\n", paraChar);
		return;
	}
 
	//删除链表 
	q = p->next;
	r = q->next;
	p->next = r;
	if (r != NULL) {
		r->previous = p;
	}
 
	// 释放空间. 
	free(q);
}

双链表优缺点:

优点:1.可找到前驱和后继,可进可退。

缺点:1.增加删除节点复杂,需要多分配一个指针存储空间。

栈和队列

栈的特点:先进后出

入栈出栈的过程:

#include <stdio.h>
#include <stdlib.h>
typedef struct lineStack{
    int data;
    struct lineStack * next;
}lineStack;
lineStack* push(lineStack * stack,int a){
    lineStack * line=(lineStack*)malloc(sizeof(lineStack));
    line->data=a;
    line->next=stack;
    stack=line;
    return stack;
}
lineStack * pop(lineStack * stack){
    if (stack) {
        lineStack * p=stack;
        stack=stack->next;
        printf("弹栈元素:%d ",p->data);
        if (stack) {
            printf("栈顶元素:%d\n",stack->data);
        }else{
            printf("栈已空\n");
        }
        free(p);
    }else{
        printf("栈内没有元素");
        return stack;
    }
    return stack;
}
int main() {
    lineStack * stack=NULL;
    stack=push(stack, 1);
    stack=push(stack, 2);
    stack=push(stack, 3);
    stack=push(stack, 4);
    stack=pop(stack);
    stack=pop(stack);
    stack=pop(stack);
    stack=pop(stack);
    stack=pop(stack);
    return 0;
}

栈满,栈空的判断条件

顺序栈:使用一组连续的内存依次保存栈中的数据,定义一个top变量来保存栈顶序号。

栈空条件是top==-1,栈空表明data中一个数都没有。

栈满条件是top==maxsize-1,由于数组下标从0开始,因此栈空的时候应该为0-1,栈满表明data中数据都占满了,所以应该取数组的最大值,maxsize-1。

队列的特点:先进先出

队列的入队出队的过程:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <conio.h>
 
using namespace std;
typedef struct node {
	int data;
	struct node *next;
}Node, *PNode;
 
typedef struct linkqueue {
	PNode first;
	PNode rear;
}Queue, *PQueue;
 
PQueue insert(PQueue link, int x)
{
	PNode s;
	s = (PNode)malloc(sizeof(node));
	s->data = x;
	s->next = NULL;
 
	if (link == NULL) {
		link = (PQueue)malloc(sizeof(Queue));
		link->first = s;
		link->rear = s;
		link->rear->next = NULL;
	}
	else {
		link->rear->next = s;
		link->rear = s;
		link->rear->next = NULL;
	}
 
	return link;
}
 
PQueue del(PQueue link)
{
	PNode p;
	if (link == NULL) {
		printf("队列为空\n");
		return NULL;
	}
 
	p = link->first;
	if (link->first != link->rear) {
		link->first = link->first->next;
	}
	else {
		printf("删除完毕\n");
	}
	free(p);
	return link;
}
 
void print(PQueue link)
{
	PNode p = link->first;
	while (p != NULL) {
		printf("%d ", p->data);
		p = p->next;
	}
	printf("\n");
}
 
int main(void)
{
	PQueue linkqueue = NULL;
 
	linkqueue = insert(linkqueue, 5);
	print(linkqueue);
	linkqueue = insert(linkqueue, 4);
	print(linkqueue);
	linkqueue = insert(linkqueue, 3);
	print(linkqueue);
	linkqueue = insert(linkqueue, 2);
	print(linkqueue);
	linkqueue = insert(linkqueue, 1);
	print(linkqueue);
	linkqueue = del(linkqueue);
	print(linkqueue);
	linkqueue = del(linkqueue);
	print(linkqueue);
	linkqueue = del(linkqueue);
	print(linkqueue);
	linkqueue = del(linkqueue);
	print(linkqueue);
	linkqueue = del(linkqueue);
	system("pause");
	return 0;
}

空队和满队的判断条件:

为了方便起见,约定:初始化建空队时,令
front=rear=0,
当队空时:front=rear
当队满时:front=rear 亦成立
因此只凭等式front=rear无法判断队空还是队满。  有两种方法处理上述问题:
(1)另设一个标志位以区别队列是空还是满。
(2)少用一个元素空间,约定以“队列头指针front在队尾指针rear的下一个位置上”作为队列“满”状态的标志。即:
队空时: front=rear
队满时: (rear+1)%maxsize=front

循环队列的基本情况:

入队,tail指针变化:
tail= (tail+1)%maxsize

出队,head指针变化:
head=( head +1)%maxsize

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 22 天,点击查看活动详情