数据结构:八大排序(冒泡,堆,插入,选择,希尔,快排,归并,计数)详解

目录

一.冒泡排序

二.堆排序

三.插入排序

四.选择排序

五.希尔排序

六.快速排序

1.Lomuto版本(前后指针法)

2.Lomuto版本的非递归算法

3.hoare版本(左右指针法)

4.挖坑法找分界值:

七.归并排序

八.计数排序

九.排序算法复杂度及稳定性分析


一.冒泡排序

冒泡排序即qsort排序,其在几大排序中属于那种效率较低的排序方式,相同条件下,qsort排序的时间要比其他排序多几十乃至几千倍,但由于其在排序过程中不会轻易改变原来数据的相对位置,因此它也算是一种较为稳定的函数(关于排序函数的稳定性我会在下文带着慢慢说明),以下是冒泡排序的具体原理与代码:

在升序条件下,不断遍历左边的数据以找其中最大者然后不断循环放在右边

//冒泡排序
void BubbleSort(int* arr, int n)
{
	int end = n;
	while (end)
	{
		int flag = 0;
		for (int i = 1; i < end; ++i)//代表每一次进入循环左边数据都会少一个
		{
			if (arr[i - 1] > arr[i])
			{
				int tem = arr[i];
				arr[i] = arr[i - 1];
				arr[i - 1] = tem;//右边比左边大就交换
				flag = 1;
			}
		}
		if (flag == 0)
		{
			break;
		}
		--end;
	}
}

看懂这样的代码之后可以仔细想想:冒泡排序在最差的情况下(即恰好每一轮的找最大值都需要它一个不漏地遍历其当时的所有数据)那么冒泡排序的时间复杂度为O(n^2)(即1一直累加到n-1)最好的情况下就是O(n),同时,在冒泡排序的每一趟过程中,假设有两个连续的一样的数据,那么冒泡排序在遍历过程中,是不会改变这两个数据的相对位置的,因此在不考虑时间复杂度的情况下,所以我称冒泡排序这种排序方式是一种稳定的排序方式

二.堆排序

堆排序的具体使用我在以下文章https://blog.csdn.net/2403_87691282/article/details/145888014?spm=1001.2014.3001.5501里有过说明,所以这里就允许我稍微偷下懒展示一下现成代码吧:

//向下调整算法
void AdjustDown(HP* hp, int parent, int n)//n是向下调整的下界范围
{
	int child = parent * 2 + 1;//公式
	while (child < n)
	{
		//建小根堆
		if (child + 1 < n && hp->arr[child + 1] < hp->arr[child])//child+1的目的是确保两个孩子结点都是有意义的结点
		{
			child++;
		}
		if (hp->arr[parent] > hp->arr[child])
		{
			swap(&hp->arr[parent], &hp->arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			//我最小的孩子都比父节点大,那么这个堆就是正常堆了,直接退出
			break;
		}
	}
}


// 升序,建⼤堆
// 降序,建⼩堆
// O(N*logN)

//基于数组的堆排序
void HeapSort(int* a, int n)
{
 
     // a数组直接建堆 O(N)
 for (int i = (n-1-1)/2; i >= 0; --i)
 {
     AdjustDown(a, n, i);
 }
     // O(N*logN)
     int end = n - 1;
 while (end > 0)
 {
     Swap(&a[0], &a[end]);
     AdjustDown(a, end, 0);
     --end;
 }
}

堆排序的时间复杂度也是比较好理解的:n*log(n),log(n)是向下调整算法每一层遍历需要的时间,n则是每一次为了出堆顶数据而进行的循环调用,由于每一次的出堆顶数据都需要调用一次向下调整算法,因此堆排序的时间复杂度为n*log(n),而且需要补充的一点是由于每一次的取堆顶出堆顶,可能会导致原待排序数据中相同数据的相对位置在堆排序后出现改变,因此也可以说尽管堆排序的效率在排序里算不错的,但其稳定性是属于不稳定的那一批

三.插入排序

插入排序的逻辑其实也不难:

1.从第一个元素开始,假设该元素已经被排序
2.取下一个元素tem,从已排序的元素序列从后往前遍历
3.如果该元素大于tem,则将该元素移到下一位
4.重复步骤3,直到找到已排序元素中小于等于tem的元素
5.tem插入到该元素的后面,若已排序所有元素都大于tem,则将tem插入到下标为0的位置
6.循环步骤2~5

void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		//记录有序序列最后一个元素的下标
		int end = i;
		//待插入的元素
		int tem = arr[end + 1];
		//单趟排
		while (end >= 0)
		{
			//比插入的数大就向后移
			if (tem < arr[end])
			{
				arr[end + 1] = arr[end];
				end--;
			}
			//比插入的数小,跳出循环
			else
			{
				break;
			}
		}
		//tem放到比插入的数小的数的后面
		arr[end  + 1] = tem;
		//代码执行到此位置有两种情况:
		//1.待插入元素找到应插入位置(break跳出循环到此)
		//2.待插入元素比当前有序序列中的所有元素都小(while循环结束后到此)
	}
}

代码补充解释:由于end作为记录有序数据的最后一个数据的变量,所以在大循环for中,end的实际值是一直在改变的,因此就需要在单趟排之后随时重置tmp的位置,而且tmp作为始终处于end后一位的变量(作为end的侦察兵,始终为了确定end是否需要与后一位交换位置而存在),在小循环while里end值每交换一次,就代表其与tmp值交换位置,因此需要在每个小循环的最后再将end值--使其回到正确的实际值,同样还有一点需要注意的就是直接插入排序的时间复杂度为O(n^2),且由于其在遍历过程中不会改变相同数据的相对位置,所以其排序性质是稳定的

四.选择排序

选择排序的思路也不算难,主要就是通过每次从待排序序列中挑一个最大再挑一个最小然后分别放在数组的两端,依次类推:

具体代码:

//选择排序
void swap(int* a, int* b)
{
	int tem = *a;
	*a = *b;
	*b = tem;
}
void SelectSort(int* arr, int n)
{
	//保存参与单趟排序的第一个数和最后一个数的下标
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		//保存最大值的下标
		int maxi = begin;
		//保存最小值的下标
		int mini = begin;
		//找出最大值和最小值的下标
		for (int i = begin; i <= end; ++i)
		{
			if (arr[i] < arr[mini])
			{
				mini = i;
			}
			if (arr[i] > arr[maxi])
			{
				maxi = i;
			}
		}
		//最小值放在序列开头
		swap(&arr[mini], &arr[begin]);
		//最大值放在序列结尾
		swap(&arr[maxi], &arr[end]);
		++begin;
		--end;
	}
}

当理解了选择排序后有一点需要注意的就是直接选择排序的时间复杂度无论是最佳情况(即待排序数据已经排序完毕)还是最差情况(每一次找最大最小都要遍历全部待排序数据)都是O(n^2),这点与冒泡排序不同,冒泡排序虽然最差情况时与直接排序相同,但在最好情况下的时间复杂度却是O(n),主要还是因为冒泡排序如果排已经拍好的数组时,其中的小循环几乎已经不需要进入执行了,但直接选择排序即便是遍历已经排好的数组也仍旧需要从头到尾遍历一遍,并且直接选择在遇到相同数据时同样是有可能会改变原始数据的相对顺序的,是不稳定的

五.希尔排序

希尔排序的逻辑可能就有些复杂:其基本思想为先选定⼀个整数(通常是gap=n/3+1),把 待排序⽂件所有记录分成各组,所有的距离相等的记录分在同⼀组内,并对每⼀组内的记录进行排序,然后gap=gap/3+1得到下⼀个整数,再将数组分成各组,进行插入排序,当gap=1时,就相当于直接插入排序,

再简单点来说就是先将待排序列进行预排序,使待排序列接近有序,然后再对该序列进行一次插入排序,此时插入排序的时间复杂度为O(N)

由于它是在直接插入排序算法的基础上进行改进⽽来的,综合来说它的效率肯定是更高的

//希尔排序
void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap>1)
	{
		//每次对gap折半操作
		gap = gap / 2;
		//单趟排序
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tem = arr[end + gap];
			while (end >= 0)
			{
				if (tem < arr[end])
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tem;
		}
	}
}

代码方面的解释也不算很难,希尔排序通过对原始数据不断的分小组细化,然后对每一个小组使用直接插入排序,从而减少直接插入排序算法里循环的次数,希尔算法里对于其中小循环的解释跟直接插入排序算法是有一样的,都是基于end与tmp的相对位置不变,然后小的往前放,大的往后放并且随时重置tmp(希尔里就是end+gap)使其一直处于end的前面而实现的算法,同样,我们再说到其时间复杂度,这里希尔算法的时间复杂度由于数学理论的局限,因此:

但唯一可以确定的是,希尔排序确实在很大程度上优化了直接插入算法

六.快速排序

快速排序我认为是几大排序里最复杂也是探究的最深的一个排序算法思想,其根本的逻辑就是通过先在原始数据任意建立一个参考点,然后通过各种方法使参考点的左边的数据小于参考点,然后使参考点右边的数据大于参考点

1.Lomuto版本(前后指针法)

思路:
1.选出一个keyi,一般是最左边或是最右边的。
2.起始时,prev指针指向序列开头,cur指针指向prev+1
3.若cur指向的内容小于keyi,则prev先向后移动一位,然后交换prev和cur指针指向的内容,然后cur指针++;若cur指向的内容大于key,则cur指针直接++,(因此在排序过程中在没遇到大于keyi参考值时,prev和cur都会在下一步之前重合,只有当遇到大于参考值时,cur才会多走一步从而拉开与prev的距离,而这样拉开一步的解雇就是使prev的下一步指向值是一个比参考值大的数,因此此时交换prev和cur就可以实现数据大的往后移动)如此进行下去,直到cur到达end+1(出界)位置,此时将keyi和prev指针指向的内容交换即可。

经过一次单趟排序,最终也能使得key左边的数据全部都小于key,key右边的数据全部都大于key。

4.然后也还是将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作

//Lomuto前后指针
int _QuickSort2(int* arr, int left, int right)
{
	int keyi = left;
	int prev = left, cur = prev + 1;
	while(cur <= right)
	{
		if (arr[cur] < arr[keyi] && ++prev != cur)
		{
			Swap(&arr[prev], &arr[cur]);
		}
		++cur;
	}
	Swap(&arr[prev], &arr[keyi]);
	return prev;
}

2.Lomuto版本的非递归算法

非递归版本的快速排序就是在Lomuto算法的基础上充分利用了栈的性质(这里有些具体的栈的操作我在之前有篇文章提过,具体请参考:https://blog.csdn.net/2403_87691282/article/details/145228592?spm=1001.2014.3001.5501),通过将数组的首尾元素存储在栈里从而实现一种类似于不断循环的二分数组的结构,而其中二分的核心即是我们最核心的代码:Lomuto算法(找参考值)

#include<stdio.h>

//定义栈的结构
typedef int STDataType;
typedef struct Stack
{
	STDataType* arr;
	int top;      //指向栈顶的位置
	int capacity;//栈的容量
}ST;

void QuickSortNonR(int* arr, int left, int right)
{
	ST st;
	STInit(&st);
	StackPussh(&st, right);
	StackPussh(&st, left);
	while (!StackEmpty(&st))
	{
		//取栈顶两次
		int begin = StackTop(&st);
		StackPop(&st);

		int end = StackTop(&st);
		StackPop(&st);

		//[begin,end]--找基准值
		int key1 = begin;
		int prev = begin, cur = prev + 1;
		//Lomuto前后指针法排序
		while (cur <= end)
		{
			if (arr[cur] < arr[key1] && ++prev != cur)
			{
				Swap(&arr[prev], &arr[cur]);
			}
			cur++;
		}
		Swap(&arr[prev], &arr[key1]);


		key1 = prev;//重新设定基准值以区分两个数组
		//begin key1 end
		//左序列【begin,key1-1】  右序列【key1+1,end】
		//要确保有效
		if (key1 + 1 < end)
		{
			StackPush(&st, end);
			StackPush(&st, key1+1);
		}
		if (begin < key1-1)
		{
			StackPush(&st, key1 - 1);
			StackPush(&st, begin);
		}
	}
	//销毁栈
	STDestroy(&st);
}
 

3.hoare版本(左右指针法)

//快速排序   hoare版本(左右指针法)
void QuickSort(int* arr, int begin, int end)
{
	//只有一个数或区间不存在
	if (begin >= end)
		return;
	int left = begin;
	int right = end;
	//选左边为key
	int keyi = begin;
	while (begin < end)
	{
		//右边选小   等号防止和key值相等    防止顺序begin和end越界
		while (arr[end] >= arr[keyi] && begin < end)
		{
			--end;
		}
		//左边选大
		while (arr[begin] <= arr[keyi] && begin < end)
		{
			++begin;
		}
		//小的换到右边,大的换到左边
		swap(&arr[begin], &arr[end]);
	}
	swap(&arr[keyi], &arr[end]);
	keyi = end;
	//[left,keyi-1]keyi[keyi+1,right]
	QuickSort(arr, left, keyi - 1);
	QuickSort(arr,keyi + 1,right);
}

在这个算法里有两点需要额外关注,一就是递归时所使用的函数的参数,这个就是三路递归法,之所以使用这样的方法是因为在快排的双指针法中,可以确保递归的平衡,避免Lomuto双指针法里产生的元素集中在左侧或者右侧而产生的递归不平衡,二就是代码过程中判断指针是否运动时的等号,之所以在相等时也让指针持续运动,是为了使数据中相同的元素最后集中在三路里的中间一路,以避免多次重复的递归计算

4.挖坑法找分界值:

思路:

        创建左右指针,⾸先从右向左找出⽐基准⼩的数据,找到后⽴即放⼊左边坑中,当前位置变为新 的"坑",然后从左向右找出⽐基准⼤的数据,找到后⽴即放⼊右边坑中,当前位置变为新的"坑",结 束循环后将最开始存储的分界值放⼊当前的"坑"中,返回当前"坑"下标(即分界值下标)

int _QuickSort(int* a, int left, int right)
{
	int mid = a[left];
	int hole = left;
	int key = a[hole];
	while (left < right)
	{
		while (left < right && a[right] >= key)
		{
			--right;
		}
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] <= key)
		{
			++left;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	return hole;
}

七.归并排序

总结来说:归并排序是一种基于分治法的高效排序算法,其核心思想是将数组逐步拆分为更小的子数组进行排序,再将有序的子数组合并为整体有序的数组

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)
	{
		return;
	}
	int mid = (right + left) / 2;
	//[left,mid] [mid+1,right]
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);

	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int index = begin1;

	 //合并两个有序数组为⼀个数组 
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin1++];
		 }
		else
		{
			tmp[index++] = a[begin2++];
		 }
	}
	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];
	}
    while (begin2 <= end2)
	{
		tmp[index++] = a[begin2++];
	}
	for (int i = left; i <= right; i++)
	{
		a[i] = tmp[i];
	}
}

void MergeSort(int* arr, int n)
{
	//外部申请临时数组空间,因此不计入空间复杂度
	int* tmp = (int*)malloc(sizeof(int*)*n);
	_MergeSort(arr, 0, n - 1, tmp);
	free(tmp);
}

八.计数排序


void CountSort(int* a, int n)
{
	int min = a[0], max = a[0];
	for (int i = 1; i < n; i++)
	{
		if (a[i] > max)
			max = a[i];
		if (a[i] < min)
			min = a[i];
	}
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail");
		return;
	}
	 memset(count, 0, sizeof(int) * range);//初始化
	// 统计次数 
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++; 
	}
	// 排序 
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			a[j++] = i + min;
		}
	}
}

九.排序算法复杂度及稳定性分析

ok阿,到这里为止,数据结构的最后一章排序也结束了,下面是整个数据结构章节的所有内容的概览图:

好了,那就到此为止吧

全文终

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/982134.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

?算法1-4 小A点菜

题目描述 不过 uim 由于买了一些书&#xff0c;口袋里只剩 M 元 (M≤10000)。 餐馆虽低端&#xff0c;但是菜品种类不少&#xff0c;有 N 种 (N≤100)&#xff0c;第 i 种卖 ai​ 元 (ai​≤1000)。由于是很低端的餐馆&#xff0c;所以每种菜只有一份。 小 A 奉行“不把钱吃…

Linux设备驱动开发之摄像头驱动移植(OV5640)

驱动移植 这里用的是NXP提供的原厂linux内核源码&#xff0c;目的是学习ov5640相关摄像头驱动的移植。如图&#xff0c;下面是linux源码自带的ov5640的驱动相关代码&#xff1a; 这个是ov5640相关头文件&#xff1a; 新建一个文件夹保存这些ov5640的驱动文件&#xff0c;打算在…

DeepSeek使用手册分享-附PDF下载连接

本次主要分享DeepSeek从技术原理到使用技巧内容&#xff0c;这里展示一些基本内容&#xff0c;后面附上详细PDF下载链接。 DeepSeek基本介绍 DeepSeek公司和模型的基本简介&#xff0c;以及DeepSeek高性能低成本获得业界的高度认可的原因。 DeepSeek技术路线解析 DeepSeek V3…

ArcGIS Pro应用指南:如何为栅格图精确添加坐标信息

一、引言 在地理信息系统中&#xff0c;栅格图是一种重要的数据类型。 然而&#xff0c;有时我们从网络上获取的栅格图并不包含坐标信息&#xff0c;这使得它们难以与其他带有坐标信息的数据进行集成和分析。 为了解决这一问题&#xff0c;我们需要对栅格图进行地理配准&…

机器学习4-PCA降维

1 降维 在数据处理过程中&#xff0c;会碰到维度爆炸&#xff0c;维度灾难的情况&#xff0c;为了得到更精简更有价值的信息&#xff0c;我们需要进一步处理&#xff0c;用的方法就是降维。 降维有两种方式&#xff1a;特征抽取、特征选择 特征抽取&#xff1a;就是特征映射…

辛格迪客户案例 | 深圳善康医药科技GMP培训管理(TMS)项目

01 善康医药&#xff1a;创新药领域的探索者 深圳善康医药科技股份有限公司自2017年创立以来&#xff0c;便扎根于创新药研发领域&#xff0c;专注于成瘾治疗药物的研究、生产与销售。公司坐落于深圳&#xff0c;凭借自身独特的技术优势与研发实力&#xff0c;在行业内逐渐崭露…

前端基础之组件

组件&#xff1a;实现应用中局部功能代码和资源的集合 非单文件组件 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"…

vue 安装依赖npm install过程中报错npm ERR! cb() never called!

解决办法&#xff1a; 步骤 1&#xff1a;清理 npm 缓存 npm cache clean --force rm -rf node_modules package-lock.json 步骤 2&#xff1a;一个第三方 npm 工具包&#xff0c;功能是 自动重试失败的 npm install 操作&#xff0c;适用于网络不稳定或依赖源不可靠的场景 …

【Oracle学习笔记】1.数据库组成对象

在Oracle数据库中&#xff0c;数据库对象是用于存储、管理和操作数据的基本构建块。以下是Oracle数据库中常见的对象类型及其简要说明&#xff1a; 1. 表&#xff08;Table&#xff09; ● 定义&#xff1a;表是存储数据的基本结构&#xff0c;由行&#xff08;记录&#xff0…

【Python · Pytorch】Conda介绍 DGL-cuda安装

本文仅涉及DGL库介绍与cuda配置&#xff0c;不包含神经网络及其训练测试。 起因&#xff1a;博主电脑安装了 CUDA 12.4 版本&#xff0c;但DGL疑似没有版本支持该CUDA版本。随即想到可利用Conda创建CUDA12.1版本的虚拟环境。 1. Conda环境 1.1 Conda环境简介 Conda&#xff1…

OpenMCU(一):STM32F407 FreeRTOS移植

概述 本文主要描述了STM32F407移植FreeRTOS的简要步骤。移植描述过程中&#xff0c;忽略了Keil软件的部分使用技巧。默认读者熟练使用Keil软件。本文的描述是基于OpenMCU_FreeRTOS这个工程&#xff0c;该工程已经下载放好了移植stm32f407 FreeRTOS的所有文件 OpenMCU_FreeRTOS工…

服务流程设计和服务或端口重定向及其websocket等应用示例

服务流程设计和服务或端口重定向及其websocket等应用示例 目录 服务或端口重定向的服务设计和websocket等应用示例 一、通用请求控制流程 1.1、入口 1.2、所有GET请求首先预检控制单元 1.3、http请求会分别自动307重定向 1.4、所有请求首先执行跨源控制单元 1.5、然后…

初次使用 IDE 搭配 Lombok 注解的配置

前言 在 Java 开发的漫漫征程中&#xff0c;我们总会遇到各种提升效率的工具。Lombok 便是其中一款能让代码编写变得更加简洁高效的神奇库。它通过注解的方式&#xff0c;巧妙地在编译阶段为我们生成那些繁琐的样板代码&#xff0c;比如 getter、setter、构造函数等。然而&…

基于javaweb的SSM+Maven高考志愿智能资讯系统设计和实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

从vue源码解析Vue.set()和this.$set()

前言 最近死磕了一段时间vue源码&#xff0c;想想觉得还是要输出点东西&#xff0c;我们先来从Vue提供的Vue.set()和this.$set()这两个api看看它内部是怎么实现的。 Vue.set()和this.$set()应用的场景 平时做项目的时候难免不会对 数组或者对象 进行这样的骚操作操作&#xff…

06 HarmonyOS Next性能优化之LazyForEach 列表渲染基础与实现详解 (一)

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; 目录 一、代码结构概览二、详细代码解析1. 数据源管理实现2. 数据结构定义3. 优化的列表项组件4. 主列表组件实现 一、代码结构概览 本文将详细解…

计算机网络:Socket网络编程 Udp与Tcp协议 第一弹

目录 1.IP地址和端口号 1.1 如何通信 1.2 端口号详解 1.3 理解套接字socket 2. 网络字节序 3. socket接口 3.1 socket类型设计 3.2 socket函数 3.3 bind函数 4. UDP通信协议 4.1 UDP服务端类 4.2 Udp服务类InitServer函数 4.3 Udp服务类Start函数 4.4 Udp服务主函…

C语言_图书管理系统_借阅系统管理

✨✨ 欢迎大家来到小伞的大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;数据结构与算法 小伞的主页&#xff1a;xiaosan_blog 本文所需对顺序表的理解&#xff1a; 注&#xff1a;由于顺序表实现图书…

玩转大语言模型——Ubuntu系统环境下使用llama.cpp进行CPU与GPU混合推理deepseek

系列文章目录 玩转大语言模型——使用langchain和Ollama本地部署大语言模型 玩转大语言模型——三分钟教你用langchain提示词工程获得猫娘女友 玩转大语言模型——ollama导入huggingface下载的模型 玩转大语言模型——langchain调用ollama视觉多模态语言模型 玩转大语言模型—…

Redis——快速入门

目录 Redis简介 安装配置(Windows) GUI工具RedisInsight的使用 十大数据类型&#xff08;5基本5高级&#xff09; 字符串String 列表List 集合Set(S) 有序集合SortedSet(Z) 哈希Hash(H) 发布订阅模式 消息队列Stream(X) 地理空间Geospatial(GEO) HyperLogLog(PF) …