顺序表的优缺点:
优点: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 天,点击查看活动详情