【雨学习】数据结构入门---线性结构的笔记及代码实现

一、连续存储【数组】

数组元素类型相同,大小相等

二、离散存储【链表】

定义:

        n个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,且只有一个后续节点

        首节点前没有前驱节点,尾节点没有后续节点

专业术语:

        首节点:第一个有效节点

        尾节点:最后一个有效节点

        头节点:是第一个有效节点前的节点,不存放有效数据,方便对链表的操作

        头指针:指向头节点的指针变量

        尾指针:指向尾节点的指针变量

只需要头指针就能对一个链表处理,指针域指向下一个节点的整体(并非单独的数据域或指针域)

分类:

        单链表

        双链表:每一个指针都有两个指针域

        循环链表:能通过任何一个节点找到其他所有节点

        非循环链表

相关算法:

        遍历、查找、清空、销毁、求长度、排序、删除节点、插入节点

        狭义的算法是与数据的存储方式密切相关的;

        广义的算法是与数据的存储方式无关的;

        泛型:利用某种技术达到的效果就是:不同的存数方式但执行的操作是一样的

连续储存数据链表的代码实现(类似于手搓数组)

#include "stdio.h"
#include "malloc.h"
#include "stdlib.h"//包含了exit函数 

//定义了一个数据类型(类似于手搓数组) 
struct Arr
{
	int * pBase;
	int len;//当前有效数组长度 
	int cnt;//当前有效元素个数
};


void init_arr(struct Arr * pArr,int length);//初始化 
bool append_arr(struct Arr * pArr,int val);//追加
bool insert_arr(struct Arr * pArr,int pos,int val);//插入
//pos的值从1开始
bool delete_arr(struct Arr * pArr,int pos,int* pVal);//删除
int get();
bool is_empty(struct Arr * pArr);
bool is_full(struct Arr * pArr);
void sort_arr(struct Arr * pArr);//排序 
void show_arr(struct Arr * pArr);
void inversion_arr(struct Arr * pArr);//倒置 

int main()
{
	struct Arr arr;
	int val;
	
	init_arr(&arr,6);
	show_arr(&arr);
	
	append_arr(&arr,4);
	append_arr(&arr,3);
	append_arr(&arr,2);
	append_arr(&arr,1);
	
	sort_arr(&arr);
	
	/*
	if(delete_arr(&arr,3,&val))
	{
		printf("删除成功!\n");
		printf("您删除的元素是:%d\n",val);
	}
	else printf("删除失败!\n");
	*/
	
	/*
	if(insert_arr(&arr,3,99))
	{
		printf("插入成功!\n");
	}
	else printf("插入失败!\n");
	
	
	if(append_arr(&arr,7))
	{
		printf("追加成功!\n");
	}
	else printf("追加失败!\n");
	*/
	show_arr(&arr);
	
	
	return 0;
}

//结构体变量不能加减乘除,但可以相互赋值 
void init_arr(struct Arr * pArr,int length)//(arr == *pArr)
{
	pArr->pBase = (int *)malloc(sizeof(int) * length);
	//pArr->pBase 等价于 (*pArr).pBase 
	if(NULL == pArr->pBase)
	{
		printf("动态内存分配失败!\n");
		exit(-1);//终止整个程序 
	}
	else
	{
		pArr->len = length;
		pArr->cnt = 0;
	}
	return;
}

bool is_empty(struct Arr * pArr)
{
	if(0 == pArr->cnt) return true;
	else return false;
}

void show_arr(struct Arr * pArr)
{
	//判断数组是否为空
	if(is_empty(pArr))//&pArr不是地址,pArr本身就是地址 
	{
		//提示用户数组为空
		printf("数组为空!\n"); 
	}
	else
	{
		for(int i=0;i<pArr->cnt;i++)
		{
			printf("%d ",pArr->pBase[i]);
			//或者是 *(pArr->pBase+i)
		}
		printf("\n");
	}
	return;
}

bool is_full(struct Arr * pArr)
{
	if(pArr->cnt == pArr->len) return true;
	else return false;
}

bool append_arr(struct Arr * pArr,int val)
{
	if(is_full(pArr))
	{
		return false;
	}
	//数组不满时才能追加
	pArr->pBase[pArr->cnt] = val;
	(pArr->cnt)++;
	return true;
}

bool insert_arr(struct Arr * pArr,int pos,int val)
{
	int i;
	
	if(is_full(pArr))
	{
		return false;
	}
	
	if(pos<1 || pos>pArr->cnt+1)
	{
		return false;
	}
	
	for(i=pArr->cnt-1;i>=pos-1;i--)
	{
		pArr->pBase[i+1] = pArr->pBase[i];
	}
	pArr->pBase[pos-1] = val;
	(pArr->cnt)++;
	
	return true;
}

bool delete_arr(struct Arr * pArr,int pos,int* pVal)
{
	int i;
	if(is_empty(pArr))
	{
		return false;
	}
	if(pos<1 || pos>pArr->cnt)
	{
		return false;
	}
	
	*pVal = pArr->pBase[pos-1];
	for(i=pos;i<pArr->cnt;i++)
	{
		pArr->pBase[i-1] = pArr->pBase[i];
	}
	(pArr->cnt)--;
	return true;
}

void inversion_arr(struct Arr * pArr)
{
	int i = 0;
	int j = pArr->cnt-1;
	while(i < j)//快速排序思想 
	{
		int tmp = pArr->pBase[i];
		pArr->pBase[i] = pArr->pBase[j];
		pArr->pBase[j] = tmp;
		i++;
		j--;
	}
	return;
}

void sort_arr(struct Arr * pArr)
{
	int i,j;
	for(i=0;i<pArr->cnt-1;i++)
	{
		for(j=0;j<pArr->cnt-1-i;j++)
		{
			if(pArr->pBase[j]>pArr->pBase[j+1])//从小到大 
			{
				int tmp = pArr->pBase[j];
				pArr->pBase[j] = pArr->pBase[j+1];
				pArr->pBase[j+1] = tmp;
			}
		}
	}
	return;
}

链表创建及相关算法的代码实现

#include "stdio.h"
#include "malloc.h"
#include "stdlib.h"//包含了exit函数
//链表的创建和链表遍历算法的实现 

typedef struct Node
{
	int data;//数据域 
	struct Node * pNext;//指针域
}NODE,*PNODE;
//NODE等价于struct Node  PNODE等价于struct Node* 

//函数声明 
PNODE create_list(void);
void traverse_list(PNODE pHead);
bool is_empty(PNODE pHead);
int length_list(PNODE pHead);
bool insert_list(PNODE,int ,int );
bool delete_list(PNODE,int ,int *);
void sort_list(PNODE);

int main(void)
{
	PNODE pHead = NULL;
	int val;
	
	pHead = create_list();
	//创建一个非循环链表,并将头节点地址赋给pHead 
	printf("当前的链表为:");
	traverse_list(pHead);
	//遍历输出
	
	//insert_list(pHead,4,33);
	//traverse_list(pHead);
	
	if(delete_list(pHead,4,&val))
	{
		printf("删除成功,删除的元素是%d\n",val);
	}
	else printf("删除失败,您删除的元素不存在\n"); 
	traverse_list(pHead);
	
	//if(is_empty(pHead)) printf("链表为空\n");
	//else printf("链表不空\n");
	
	//int len = length_list(pHead);
	//printf("链表的长度是:%d\n",len);
	
	//sort_list(pHead);
	//printf("排序后的链表为:");
	//traverse_list(pHead);
	
	return 0;
}

PNODE create_list(void)
{
	int len;//存放有效节点的个数 
	int i;
	int val;//临时存放用户输入节点的值 
	
	//分配一个不存放有效数据的头节点 
	PNODE pHead = (PNODE) malloc(sizeof(NODE));
	if(NULL == pHead)
	{
		printf("分配失败,程序终止\n");
		exit(-1);
	}
	
	PNODE pTail = pHead;
	//pTail指向当前的尾节点 
	pTail->pNext = NULL;
	//避免len=0,要把头节点指针域清空 
	 
	printf("链表节点的个数:len=");
	scanf("%d",&len);
	
	for(i=0;i<len;i++)
	{
		printf("输入第%d个节点的值:",i+1);
		scanf("%d",&val);
		
		//将pNew挂到当前尾节点的后面 
		PNODE pNew = (PNODE) malloc(sizeof(NODE));
		if(NULL == pNew)
		{
			printf("分配失败,程序终止\n");
			exit(-1);
		}
		pNew->data = val;
		pTail->pNext = pNew;
		pNew->pNext = NULL;
		pTail = pNew;
	}
	
	return pHead;
}

void traverse_list(PNODE pHead)
{
	PNODE p = pHead->pNext;
	while(NULL != p)
	{
		printf("%d ",p->data);
		p = p->pNext;
	}
	printf("\n输出完毕\n");
	return;
}

bool is_empty(PNODE pHead)
{
	if(pHead->pNext == NULL)
	{
		return true;
	}
	else return false;
}

int length_list(PNODE pHead)
{
	PNODE p = pHead->pNext;
	int num = 0;
	while(NULL != p)
	{
		num++;
		p = p->pNext;
	}
	return num;
}

void sort_list(PNODE pHead)
{
	int i,j,t;
	PNODE p,q;
	int len = length_list(pHead);

	for(i=0,p=pHead->pNext;i<len-1;i++,p=p->pNext)
	{
		for(j=i+1,q=p->pNext;j<len;j++,q=q->pNext)
		{
			if(p->data > q->data)//类似于数组中的:a[i]>a[j] 
			{
				t = p->data;
				p->data = q->data;
				q->data = t;
			}
		}
	}
	return;
}

//在pHead所指向链表的第pos个节点的前面插入一个新的节点,该节点的值是val,并且pos的值从1开始 
bool insert_list(PNODE pHead,int pos,int val)
{
	int i = 0;
	PNODE p = pHead;
	
	while(NULL!=p && i<pos-1)
	{
		p = p->pNext;
		++i;
	}
	
	if(i>pos-1 || NULL==p) return false;
	
	PNODE pNew = (PNODE)malloc(sizeof(NODE));
	if(NULL == pNew)
	{
		printf("动态内存分配失败\n");
		exit(-1);
	}
	pNew->data = val;
	PNODE q = p->pNext;
	p->pNext = pNew;
	pNew->pNext = q;
	
	return true;
}

bool delete_list(PNODE pHead,int pos,int *pVal)
{
	int i = 0;
	PNODE p = pHead;
	
	while(NULL!=p->pNext && i<pos-1)
	{
		p = p->pNext;
		++i;
	}
	
	if(i>pos-1 || NULL==p->pNext) return false;
	
	PNODE q = p->pNext;
	*pVal = q->data;
	
	p->pNext = p->pNext->pNext;
	free(q);
	q = NULL;
	
	return true;	
}

(一)线性结构的两种常见应用之一:栈

定义:

          一种可以实现“先进后出”的存储结构

          栈类似于箱子

          头部用top,尾部用bottom

        (把链表的一些功能砍掉,再添加一些功能)

分类:

          静态栈

          动态栈

算法:

          出栈

          压栈

应用:

          函数调用

          中断

          表达式求值(计算器)

          内存分配

          缓冲处理

          迷宫

栈的代码实现

#include "stdio.h"
#include "malloc.h"
#include "stdlib.h"

typedef struct Node
{
	int data;
	struct Node * pNext;
}NODE, * PNODE;

typedef struct Stack
{
	PNODE pTop;
	PNODE pBottom;
}STACK, * PSTACK;

void init(PSTACK);
void push(PSTACK,int);
void traverse(PSTACK);
bool pop(PSTACK,int*);
void clear(PSTACK);

int main(void)
{
	STACK S;
	int val;
	
	init(&S);//初始化
	push(&S,1);//压栈
	push(&S,2);//压栈
	push(&S,3);//压栈
	push(&S,4);//压栈
	push(&S,5);//压栈
	push(&S,6);//压栈
	traverse(&S);//遍历输出 
	
	clear(&S);//栈保留,清空栈
	//traverse(&S);//遍历输出  
	
	if(pop(&S,&val))//出栈
	{
		printf("出栈成功,出栈元素是%d\n",val);
	}
	else printf("出栈失败!\n");
	
	traverse(&S);//遍历输出 
	
	return 0;
}

void init(PSTACK pS)
{
	pS->pTop = (PNODE)malloc(sizeof(NODE));
	if(NULL == pS->pTop)
	{
		printf("动态内存分配失败\n");
		exit(-1);
	}
	else
	{
		pS->pBottom = pS->pTop;
		pS->pTop->pNext = NULL;
		//或写pS->pBottom->pNext = NULL;
		//把创建空间的指针域清空 
	}
}

void push(PSTACK pS,int val)
{
	PNODE pNew = (PNODE)malloc(sizeof(NODE));
	pNew->data = val;
	pNew->pNext = pS->pTop;
	pS->pTop = pNew;
	return;
}

void traverse(PSTACK pS)
{
	PNODE p = pS->pTop;
	
	while(p != pS->pBottom)
	{
		printf("%d ",p->data);
		p = p->pNext;
	}
	printf("\n");
	
	return ;
}

bool is_empty(PSTACK pS)
{
	if(pS->pTop == pS->pBottom)
	{
		return true;
	}
	else return false;
}


//把pS所指向的栈出栈一次,把出栈的元素存入val中 
bool pop(PSTACK pS,int *pVal)
{
	if(is_empty(pS))//pS本身存放的就是S的地址 
	{
		return false;
	}
	else
	{
		PNODE r = pS->pTop;
		*pVal = r->data;
		pS->pTop = r->pNext;
		free(r);
		r = NULL;
		
		return true;
	}
}

void clear(PSTACK pS)
{
	if(is_empty(pS))
	{
		return ;
	}
	else
	{
		PNODE p = pS->pTop;
		PNODE q = NULL;
		
		while(p != pS->pBottom)
		{
			q = p->pNext;
			free(p);
			p = q;
		}
		pS->pTop = pS->pBottom;
		return ;
	}
}

(二)线性结构的两种常见应用之二:队列

定义:

          一种可以实现”先进先出“的存储结构

          头部用front,尾部用rear

分类:

          链式队列 --- 用链表实现

          静态队列 --- 用数组实现(把数组的一些功能砍掉,再添加一些功能)

          静态队列通常都必须是循环队列

    附:循环队列的讲解:      

        1.静态队列为什么必须是循环队列

                front指向第一个元素,rear指向最后一个元素的下一个元素*

                f和r都只能加,f和r要循环

       2.循环队列需要几个参数来确定        需要2个参数来确定

                Front

                Rear

       3.循环队列各个参数的含义

                2个参数在不同场合有不同含义

                建议初学者先记住,然后慢慢体会          

                1)队列初始化

                          front和rear的值都是零

                2)队列非空

                          front代表的是队列的第一个元素

                          rear代表的是队列的最后一个有效元素的下一个元素

               3)队列空

                          front和rear的值相等,但不一定是零

      4.循环队列入队伪算法讲解

                1.将值存入r所代表的位置

                2.r=r+1 //错误写法

                正确写法为:r=(r+1)%数组长度

      5.循环队列出队伪算法讲解

                 f=(f+1)%数组长度

      6.如何判断循环队列是否已空伪算法

                f和r相等时队列为空

      7.如何判断循环队列是否为满伪算法

                预备知识:

                          front的值可能比rear大

                          front的值也完全有可能比rear小

                          当然也可能两者相等

                两种方法:

                          1.多增加一个标志参数(记录存储元素个数)

                          2.少用一个元素

                            如果r和f的值紧挨着,则队列已满

                            用C语言伪算法表示就是:

                              /*

                              if( (r+1)%数组长度==f ) 已满

                              else 不满

                              */

队列算法:

          入队

          出队

队列的具体应用:

          所有和时间有关的操作都有队列的影子(优先级)

循环队列的代码实现

#include "stdio.h"
#include "bits/stdc++.h"
#include "malloc.h"
#include "stdlib.h"
using namespace std;

typedef struct Queue
{
	int * pBase;//Pbase是一个数组的首地址 
	int front;
	int rear;
}QUEUE;

void init(QUEUE *);
bool en_queue(QUEUE *,int val);
void traverse_queue(QUEUE *);
bool full_queue(QUEUE *);
bool out_queue(QUEUE *,int * );
bool emput_queue(QUEUE *);

int main()
{
	QUEUE Q;
	int val;
	
	init(&Q);
	en_queue(&Q,1);
	en_queue(&Q,2);
	en_queue(&Q,3);
	en_queue(&Q,4);
	en_queue(&Q,5);
	en_queue(&Q,6);
	en_queue(&Q,7);
	en_queue(&Q,8);
	//只能存储1-5的数 
	
	traverse_queue(&Q);

	if(out_queue(&Q,&val))
	{
		printf("出队成功,您出队的元素是%d\n",val);
	}
	else printf("出队失败\n");
	
	traverse_queue(&Q);
	
	return 0;
}

void init(QUEUE *pQ)
{
	pQ->pBase = (int *)malloc(sizeof(Queue) * 6);
	pQ->front = 0;
	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;
	}
}

void traverse_queue(QUEUE *pQ)
{
	int i = pQ->front;
	
	while(i != pQ->rear)
	{
		printf("%d ",pQ->pBase[i]);
		i = (i+1) % 6;
	}
	printf("\n");
	
	return;
}

bool emput_queue(QUEUE *pQ)
{
	if(pQ->front == pQ->rear)
	{
		return true;
	}
	else return false;
}

bool out_queue(QUEUE *pQ,int *pVal)
{
	if(emput_queue(pQ))
	{
		return false;
	}
	else
	{
		*pVal = pQ->pBase[pQ->front];
		pQ->front = (pQ->front+1)%6;
		
		return true;
	}
}

专题:递归

          定义:

                  一个函数自己直接或间接调用自己

          阶乘满足三个条件:

                  1.递归必须得有一个明确的终止条件

                  2.该函数所处理的数据规模必须在递减

                  3.这个转化必须是可解的

          循环和递归

                  递归:

                            易于理解

                            速度慢

                            存储空间浪费多

                    循环:

                            不易理解

                            速度快

                            存储空间浪费少

  举例:    1.1+2+3+...+100的和

                 2.求阶乘

                 3.汉诺塔

                 4.走迷宫

  递归的应用:

            树和森林就是以递归的方式定义的

            数和图的很多算法都是以递归来实现的

            很多数学公式就是以递归的方式定义的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Electronic_Rain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值