【数据结构(一)】用C语言实现(双向、带头、循环)链表、栈、队列、递归、二叉树等基本数据结构

目录

前言

一、单向链表

1.简介

2.代码与图解

二、带头双向循环链表         

1.简介

2.代码与图解

3.补充

三、栈   

1.简介

2.代码与图解

四、队列

1.简介

2.代码与图解

五、二叉树

1.了解递归和递归与栈的关系

2.树的基本概念

3.树的存储结构(待更) 

4.二叉树

5.前、中、后序遍历(代码与图解)

6.补充

Summery💐


前言

什么是数据结构:

百度百科:数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合

为何要学习数据结构:

 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法索引技术有关。

一、单向链表

1.简介

链表作为一种基本的数据结构在程序开发过程当中经常会使用到。对C语言来说链表的实现主要依靠结构体和指针;

链表的一个结点如下⬇️:

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

如图⬇️,为一个基本的单向链表:

掌握单向链表的增 、 删 、 查 、 改   ,为之后的带头、双向、循环链表做铺垫。

2.代码与图解

先看一下实现链表需要的函数⬇️

void SListPrint(SLTNode* phead);
void SListPushBack(SLTNode** pphead, SLTDataType x);
void SListPushFront(SLTNode** pphead, SLTDataType x);
void SListPopFront(SLTNode** pphead);
void SListPopBack(SLTNode** pphead);
SLTNode* SListFind(SLTNode*phead, SLTDataType x);
void SListInsert(SLTNode** pphead,SLTNode*pos, SLTDataType x);
void SListErase(SLTNode** pphead, SLTNode* pos);

①.尾插          1.创建一个结构体指针(newnode),判断头指针是否为空(注意是*pphead);

                     2.若不为空:备份头指针(tail),用while循环找到链表的尾部,最后将尾部的下一个指向新创建的newnode。

void SListPushBack(SLTNode **pphead, SLTDataType x)
	{

	SLTNode* newnode = BuySListNode(x);

		if (*pphead == NULL)
		{
			*pphead = newnode;
		}
		else
		{
			SLTNode* tail = *pphead;
			while (tail->next != NULL)
			{
				tail = tail->next;
			}
			tail->next = newnode;
		}
	}

 其中BuySListNode函数即创建并初始化(注意返回值为SLTNode*):

SLTNode*BuySListNode(SLTDataType x)                               
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

(理解了尾插,头插就是小case啦)

②.头插         1. newnode->next  ←  *pphead;  (“旧头”给“新头”的next👌)   

                     2.*pphead ← newnode;(将“新头”的地址给“旧头”,及把newnode变成新头);

void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

③.头删         ※1.若将头一刀砍,身子就找不到取出了🤣,意思是要将头指向的下一个保存起来(next);

                     2.free掉旧头,*pphead的地址变为next的地址(及将链表的第二个数变成新头)*pphead next。

void SListPopFront(SLTNode** pphead)
{
	 SLTNode*next= ( * pphead)->next;
	 free(*pphead);
	 *pphead = next;
}

④尾删          (别以为头删简单,就轻视尾删)

                     1.两种特殊情况:链表为空  和  只有一个头,想到了后面删除就简单了;

                     2.若有两个及以上:同样第一步是找到链表的尾部,但是直接将尾部置为空,那么倒数第二个的next将无家可归🤦‍♂️

所以,定义一个prev,当next的小老弟,跟在next的后面,当next指向尾部时,prev就自然是倒数第二了!

void SListPopBack(SLTNode** pphead){
	if (*pphead = NULL)
		return;
	else if ((*pphead)->next = NULL)
	{
		free(*pphead);
		*pphead = NULL;
			
	}
	else
	{
		SLTNode* prev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		prev->next = NULL;
	}
}

重头戏来了🙌                                                                 

⑤插入          1.往pos之前插入,自然需要找到pos的位置(查找函数在下方),再将pos转入SListInsert函数之内,最后将需要插                         入的值传入;

                     2.如果pos的地址就为pphead,那么就是上面说的头插

                     3.同样定义prev,用它借助while循环找到pos的前一位,之后开辟一个newnode,通过next将prev----newnode----                            pos三者连接起来。

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
	if (pos == *pphead)
		SListPushFront(pphead, x);

	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySListNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

⑥删除          1.思路与 插入 类似:如果pos的地址就为pphead,那么就算是尾删了;

                     2.举一反三:定义prev,用它找到pos的前一位并用prev指向它,此时用next将prev与pos的下一位(pos->next)链接起来就能将pos指向的位置给丢掉啦!(别玩了用free掉pos,将他丢干净😎)

void SListErase(SLTNode** pphead, SLTNode* pos) {
	if (pos == *pphead)
		SListPopBack(pphead);

	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

 补充

①查找(pos)        1.※( *phead是一级指针),传入x,查找data为x的指针cur并将其返回(return cur)。

SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)     //特别注意=  与 ==的区别;
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

②输出        有了上述的理解,这儿就显得很easy啦!

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

二、带头双向循环链表         

1.简介

学习了单向链会发现它理解起来容易但是实现起来复杂,而接下来的链表理解起来相对复杂,实现起来却很容易;

这条链表的一个结点如下:

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

                                                    如图⬇️,为一个基本的单向链表:

 ※ 带头:这里的phead为一个不存数据的指针 ,所以传一级指针;可以说phead为假头phead->next才是真头;

※ 双向:eg.之前尾删需要两个指针,这里的优点就非常明显了,一举两得,得到一个节点可以找到它的前一个(->prev和后一个(->next);

※ 循环尾结点的下一个节点不为空,而是假头(tail->next == phead);同样phead->prev == tail,这样能直接找到尾指针。

2.代码与图解

忽略掉简单的初始化、打印、销毁,需实现的函数如下:

void ListPushBack(ListNode* phead, LTDataType x);
void ListPushFront(ListNode* phead, LTDataType x);
void ListPopFront(ListNode* phead);
void ListPopBack(ListNode* phead);
void ListInsert(ListNode* pos, LTDataType x);  //插入
void ListErase(ListNode* pos);  //删除

这里我们先写出插入与删除,之后有了上文的优点,其他的会变得非常简单:

①.插入

图解⬇️找到pos前的first,再将newnode与两者双向链接。

 代码如下:

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* first = pos->prev;
	ListNode* newnode = BuyListNode(x);
	first->next = newnode;
	newnode->prev = first;
	newnode->next = pos;
	pos->prev = newnode;
}

②.删除

图解⬇️

若直接删除pos这个结点,这个链表就一刀两段了,所以要找到pos的头尾,再将它们链接起来。

 

代码如下:

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* next = pos->next;
	ListNode* prev = pos->prev;
	prev->next = next;
	next->prev = prev;
	free(pos);
	pos = NULL;
}

有了插入与删除,接下来的代码就会非常简单了;

③ 尾 插:

参考   ①插入   这里传phead过去,到ListInsert函数里pos指向phead,因为是循环链表,可以往phead前找到first(及为链尾),再往firstphead之间插入就实现尾插了。⬇️

void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);

	ListInsert(phead, x);
}

④ 头 插:

对比尾插,思考一下🤔🤔🤔,这里往ListInsert函数里传入的就是  phead->next  了,

也就是在phead与phead->next之间插入;

void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	
	ListInsert(phead->next, x);
}

 ⑤&⑥ 头 删 与 尾 删

参考 ② 删除  若要实现头删,很好想象,就是传假头指向的真头(phead->next)

void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	ListErase(phead->next);
}

同样,因为链表循环,尾删 就是删除 phead->prev;

void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	ListErase(phead->prev);
}

3.补充

①. 掌握带头 双向 循环 链表也就是掌握了2 × 2 × 2 = 8种链表;

②. 简单的查找与打印就是遍历链表,这里就不说了;

③.Destroy:建立两个指针,将phead之后的结点都free掉,最后将free(phead);

void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = NULL;
		cur = next;
	}
	free(phead);
	phead = NULL;
}

三、栈   

1.简介

①. 栈线性表的一种特殊的存储结构。与学习过的线性表的不同之处在于栈只能从表的固定一端对数据进行插入和删除操作,另一端是封死的;

②. 栈的“先进后出”原则:先进:数据元素用栈的数据结构存储起来,称为“入栈”,也叫“压栈”,先进去的被压在最底端;

                                       后出:数据元素从栈结构中提取出来,称为“出栈”,也叫“弹栈”,也就是后进入的比先进入的先出来;

③. 如下图⬇️,我们可以把栈比作手枪的弹夹,看得出来,先被进弹夹的子弹是后出弹夹的;

 4.我们用数组存储栈的数据,为了知道栈是否溢出,需要一个变量记录数组的容量capacity,最后用变量top记录栈顶,以实现出栈。将三者放入一个结构体内形成⬇️

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

(参考书籍 《大话数据结构》 )                                                                                                      

2.代码与图解

先看一下栈实现需要的函数⬇️

void StackInit(ST* ps);    //初始化操作,建立一个空栈(ps)
void StackDestory(ST* ps);    //若栈存在,则销毁它,并清空
void StackPush(ST* ps,STDataType x);    // ※ 插入新数据x到栈ps中并成为栈顶数据
void StackPop(ST* ps);    //删除栈S中栈顶数据
STDataType StackTop(ST* ps);    //若栈存在且非空,返回ps的栈顶数据
int StackSize(ST* ps);    //返回栈的数据个数
bool StackEmpty(ST* ps);    //栈为空,返回true,否则返回false

①.初始化:

void StackInit(ST* ps){
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	ps->capacity = 4;
	ps->top = 0;
}

②入栈:  1.判断是否入栈之后会出现栈溢出,若栈顶 == 容量(capacity),则relloc对数组a增容,再改变capacity记录下现在的容量;

void StackPush(ST* ps, STDataType x){
	assert(ps);
	if (ps->capacity == ps->top)
	{
		STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		else
		{
			ps->a = tmp;
			ps->capacity *= 2;
		}
	}
	ps->a[ps->top] = x;
	ps->top++;
}

③.出栈      ※只需要将栈顶(top)-1就可以了,当再次入栈时,新数据会替代top-1之后指向的位置

void StackPop(ST* ps){
	assert(ps);
	assert(ps->top> 0);
	ps->top--;
}

④.栈顶数据、栈大小、判断是否为空栈,均用top实现,较为容易,这里就不详述了,代码可进我Gitee查询。

四、队列

1.简介

①. 队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表;

②. 遵循先进先出(First In First Out)原则,简称FIFO

③. 从名字就能想象,队列与我们平常生活中排队是一样的(但是没有插队这一说法😅)⬇️

④. 要实现一头出,一头进,并且省去从头遍历找到尾,我们定义两个指针,head与tail

⑤. 队列可以像栈写成数组也可以像链表写成结点,这里区分栈,我把它写成结点,

这儿又有与链表不同的创建结构体方式⬇️: 

 

像左边这样,函数只需要用一级指针接收

接下来通过代码再次理解。

2.代码与图解

先看一下队列实现需要的函数⬇️

void QueueInit(Queue* pq);    //初始化
void QueueDestroy(Queue* pq);    //销毁队列
void QueuePush(Queue* pq, QDataType x);    //若队列存在,插入新数据x到队列中并成为队尾元素
void QueuePop(Queue* pq);    //若队列存在,删除对头数据
QDataType QueueFront(Queue* pq);    //返回对头数据
QDataType QueueBack(Queue* pq);    //返回队尾数据
int QueueSize(Queue* pq);    //返回队列长度
bool QueueEmpty(Queue* pq);    //判断队列是否为空(true/false)

 ①. 尾插   

1.因为创建了两个结构体,所以这里要多加注意结构体指针的类型(QNode和Queue

2. 同时,老套路了,要考虑队列是否为空

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 = pq->tail->next;
	}
}  

 ②. 头删 

1.若只有head一个结点(也就是head与tail为同一个结点),直接删除掉head;

2.若有多个结点,不能直接删除头节点,之前也讲到过;定义next指向head->next,再删除head,

将next作为新的头节点。

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head);  
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

③.之后的函数也与前面极其类似,这里也不占用时间了。(详细代码请看Gitee

五、二叉树

1.了解递归和递归与栈的关系

 (参考书籍 《大话数据结构》、《数据结构与算法图解》)

①. 函数调用自身,就叫作递归,可以将其大概理解成一种特殊的循环

②. 既然是函数调用自己,那么如果无止境的调用将会是一件非常可怕的事,这就需要一个判断来将这个自我调用往回走(return);

接下来用一个经典的例子:斐波那契数列   来见识一下递归⬇️

 数学上可表示为:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*)。第0项是0,第1项是第一个1。此数列从第2项开始,每一项都等于前两项之和。(其中的F()也就和C语言中的函数一样,将其转化成代码如下⬇️)

 

③.  当 i= 0 或 i = 1时,很好理解,传入Fbi函数返回0或1(这就是函数不断调用自己到最后返回来的条件),但是当 i 是一个较大的数(i = 5)时又该怎么理解呢?我们画图分析⬇️:

  

注意:图中圆圈里的数字是n,而不是返回值,往回递归时:圆圈里执行的是两个返回值相加,再对结果返回上一个递归中

※补充④ .递归与栈的关系:简单来说,递归分为两个部分(正序与逆序),那和栈怎么就搭边了呢?

在计算机系统内部,在递归正序存储某些数据,并在后面又以存储的逆序恢复这些数据,以提供之后使用的需求,与栈中的入栈与出栈恰恰相似,因此,编译器会用栈实现递归

2.树的基本概念

 ①. 前言:之前学习的都是链表和顺序表,二树由根与子树构成,是一个一对多的结点类型的结构;这里将我们学过的递归思想运用到接下来的学习中

②树的注意事项:

※根结点唯一,如图⬇️,I 的根节点有两个,分别是 D 和 E;

 ※子树互不相交,如图⬇️,D和E分别是B和C的子树,而他们相交是错误的;

③. 关于树的其他概念较为繁琐,这里就暴力阐述了⬇️ 

3.树的存储结构(待更) 

4.二叉树

①. 二叉树是一种特殊的树,只有根、左子树、右子树组成(可以只有左子树,也可以只有右子树),每个结点的子结点最多不超过两个;

②. ※满二叉树: 若层数为n,那么最后一层的结点个数为 2^n-1 ,通俗来讲就是最后一层的“叶子”一片不少⬇️

     ※完全二叉树:若层数为n,那么最后一层的结点个数x满足 1≤ x ≤ 2^n-1,就是被摘了几片叶子的满二叉树⬇️

 ③. 二叉树的性质:
※.若规定根节点的层数为1,则一棵非空_二叉树的第i层上最多有 2^(i-1) 个结点;✔️
※.若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2^h- 1;✔️
※.对任何一棵二叉树,如果度为0的叶结点个数为n0,度为2的分支结点个数为n2则有 n0= n2+ 1,eg.⬇️


※.若规定根节点的层数为1,具有n个结点的满二叉树的深度, h=logN;✔️

5.前、中、后序遍历(代码与图解)

①. 数组:我们可以简单地用数组存储二叉树的数据,再用下标表示结点位置(一颗满二叉树从根开始,每一层从左到右一次增大),但当不是结构整齐的满二叉树时(eg.只有左子树),也要按照满二叉树给数组分配空间,这样很不划算;

②. 结构体:从根开始,用两个指针分别指向左子树和右子树,再存储该结点的数据,就像链表中的一个结点一样,不同的是二叉树的一个结点指向了两个子结点,我们把这样的结构称为二叉链表,不废话了,看图⬇️

typedef char BTDataType;
typedef struct BinaryTreeNpod
{
	struct  BTNode* left;
	struct BTNode* right;
	BTDataType data;
}BTNode;

 二叉树遍历

①. 前序:传入根结点,,从根结点开始,递归遍历左子树,若不为空,则继续递归遍历左子树,若为空,返回;之后递归遍历右子树,(根 -> 左子树 -> 右子树

void PrevOrder(BTNode* root)//前序
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

图解:⬇️

 ②. 中序:传入根结点,递归遍历左子树,到最后为空返回,访问根,之后遍历右子树;(左子树 -> 根 -> 右子树)

void InOrder(BTNode* root)//中序
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}

③. 后序:传入根结点 ,递归遍历左子树,到最后为空返回,再递归遍历右子树,最后访问根;(左子树 -> 右子树 -> 根)

void PostOrder(BTNode* root)//后序
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->data);
}

图解:⬇️ 

 ④. 层序:(待更)

6.补充

①. 计算树总共的结点个数:※其中记录个数的 size 要定义为全局变量,因为每次递归局部变量会因出函数而销毁;或者用传指针

int size = 0;
void TreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	else
	{
		size++;
	}
	TreeSize(root->left);
	TreeSize(root->right);
}

②. ※计算叶结点的总数:

nt TreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	else
	return root->left ==NULL&&root->right == NULL ? 1 : TreeLeafSize(root->left)+TreeLeafSize(root->right);
}

 


 

Summery💐

•  以上就是大部分基本的数据结构了,文章加上代码的字数一共有一万字了,完全看完是不太现实的,找到自己的不足及时去弥补才是最重要的;

  以后有学到更复杂的数据结构我会及时更新博客🙈

  接下来我们一起来学习一些简单的排序算法⬇️

https://blog.csdn.net/Dusong_/article/details/127749130?spm=1001.2014.3001.5502

16进制10进制.txt 32.txt asm.txt Crctable.txt C标志符命名源程序.txt erre.txt erre2.txt ff.txt for循环的.txt list.log N皇后问题回溯算法.txt ping.txt re.txt source.txt winsock2.txt ww.txt 万年历.txt 万年历的算法 .txt 乘方函数桃子猴.txt 乘法矩阵.txt 二分查找1.txt 二分查找2.txt 二叉排序树.txt 二叉树.txt 二叉树实例.txt 二进制数.txt 二进制数2.txt 余弦曲线.txt 余弦直线.txt 傻瓜递归.txt 冒泡排序.txt 冒泡法改进.txt 动态计算网络最长最短路线.txt 十五人排序.txt 单循环链表.txt 单词倒转.txt 单链表.txt 单链表1.txt 单链表2.txt 单链表倒序.txt 单链表的处理全集.txt 双链表正排序.txt 反出字符.txt 叠代整除.txt 各种排序法.txt 哈夫曼算法.txt 哈慢树.txt 四分砝码.txt 四塔1.txt 四塔2.txt 回文.txt 图.txt 圆周率.txt 多位阶乘.txt 多位阶乘2.txt 大加数.txt 大小倍约.txt 大整数.txt 字符串查找.txt 字符编辑.txt 字符编辑技术(插入和删除) .txt 完数.txt 定长串.txt 实例1.txt 实例2.txt 实例3.txt 小写数字转换成大写数字1.txt 小写数字转换成大写数字2.txt 小写数字转换成大写数字3.txt 小字库DIY-.txt 小字库DIY.txt 小孩分糖果.txt 小明买书.txt 小白鼠钻迷宫.txt 带头结点双链循环线性表.txt 平方根.txt 建树和遍历.txt 建立链表1.txt 扫描码.txt 挽救软盘.txt 换位递归.txt 排序法.txt 推箱子.txt 数字移动.txt 数据结构.txt 数据结构2.txt 数据结构3.txt 数组完全单元.txt 数组操作.txt 数组递归退出.txt 数组递归退出2.txt 文件加密.txt 文件复制.txt 文件连接.txt 无向图.txt 时间陷阱.txt 杨辉三角形.txt 单元加.txt 操作.txt 桃子猴.txt 桶排序.txt 检出错误.txt 检测鼠标.txt 汉字字模.txt 汉诺塔.txt 汉诺塔2.txt 灯塔问题.txt 猴子和桃.txt 百鸡百钱.txt 矩阵乘法动态规划.txt 矩阵转换.txt 硬币分法.txt 神经元模型.txt 穷举搜索法.txt 符号图形.txt 简单数据库.txt 简单计算器.txt 简单逆阵.txt 线性顺序存储结构.txt 线索化二叉树.txt 绘制圆.txt 编随机数.txt 网络最短路径Dijkstra算法.txt 自我复制.txt 节点.txt 苹果分法.txt 螺旋数组1.txt 螺旋数组2.txt 试题.txt 诺汉塔画图版.txt 读写文本文件.txt 货郎担分枝限界图形演示.txt 货郎担限界算法.txt 质因子.txt 输出自已.txt 迷宫.txt 迷宫问题.txt 逆波兰计算器.txt 逆矩阵.txt 逆阵.txt 递堆法.txt 递归桃猴.txt 递归车厢.txt 递推.txt 逻辑移动.txt 链串.txt 链.txt 链表十五人排序.txt 链表递归).txt 链队列.txt 队列.txt 阶乘递归.txt 阿姆斯特朗数.txt 非递归.txt 顺序.txt 顺序表.txt 顺序队列.txt 骑士遍历1.txt 骑士遍历2.txt 骑士遍历回逆.txt 黑白.txt
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Dusong_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值