2.顺序表

1.线性表

  • 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使

    用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…

  • 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,

    线性表在物理上存储时,通常以数组和链式结构的形式存储。

image-20220823183653823

2.顺序表

2.1概念及结构

  • 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

顺序表一般可以分为:

  1. 静态顺序表:使用定长数组存储元素
#define N 10000
typedef int SLDataType;   //方便修改存储数据的类型

struct SeqList
{
	SLDataType a[N];
	int size; // 存储数据的个数
};
  1. 动态顺序表:使用动态开辟的数组存储。
typedef int SLDataType;

typedef struct SeqList
{
	SLDataType* a;
	int size;      // 存储数据的个数
	int capacity;  // 存储空间的大小
}SL;

2.2动态顺序表接口实现

  • 静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。

SequentListInit(初始化)

void SLInit(SL* psl)
{
	assert(psl);
	// 数组的地址初始化为空
	psl->a = NULL;
    // 数组的容量,和当前数组的大小
	psl->capacity = psl->size = 0;
}

SequentListDestory(销毁)

void SLDestory(SL* psl)
{
	assert(psl);
    free(psl->a);
    // 数组的地址置为空,防止被销毁后,还有人进行访问
	psl->a = NULL;
    // 数组的容量,和当前数组的大小
	psl->capacity = psl->size = 0;
}

SequentListPushBack(尾插)

perror

void perror ( const char * str );
  • Print error message

  • Interprets the value of errno as an error message, and prints it to stderr (the standard error output stream, usually the console(n. 控制台)), optionally(adv. 可选择地,随意地) preceding it with the custom message specified in str.

将errno的值解释为错误消息,并将其打印到stderr(标准错误输出流,通常是控制台),可选地在其前面加上在str中指定的自定义消息。

exit

void exit (int status);

SLCheckCapacity(检查容量)

void SLCheckCapacity(SL* psl)
{
	// 检查容量
    // 如果数组的大小与数组的容量相等,此时还要进行新的数据插入,那么就需要进行扩容
	if (psl->size == psl->capacity)
	{
        // 如果psl->capacity等于0,那么新的容量就是4
        // 如果psl->capacity不等于0,那么新的容量大小为psl->capacity * 2
		int newCapcity = psl->capacity == 0 ? 4 : psl->capacity * 2;
        
        // 使用函数realloc对原有的空间进行扩容
		SLDataType* tmp = (SLDataType*)realloc(psl->a, newCapcity*sizeof(SLDataType));
		if (tmp == NULL)
		{
            // 扩容失败,则打印错误信息
			perror("realloc fail");
			return;
			//exit(-1);
		}

        // 将扩容后的地址,交给a
		psl->a = tmp;
        // 将数组容量改为扩容后的新容量
		psl->capacity = newCapcity;
	}

}
// SequentListPushBack(尾插)
void SLPushBack(SL* psl, SLDataType x)
{
	assert(psl);

    // 进行尾插前,需要检查当前数组的容量
    // 如果容量不够,那么需要进行扩容
	SLCheckCapacity(psl);

	psl->a[psl->size] = x;
    // 数据插入后,将数组下标向后移动
	psl->size++;
}

SequentListprint(打印)

void SLPrint(const SL* psl)
{
	assert(psl);
    // 打印数组中的所有数据
	for (int i = 0; i < psl->size; ++i)
	{
		printf("%d ", psl->a[i]);
	}
	printf("\n");
}

SequentListPushFront(头插)

void SLPushFront(SL* psl, SLDataType x)
{
	assert(psl);
    // 头插前,先要检查数组的容量,防止越界
	SLCheckCapacity(psl);

	// 挪动数据(想要头插,就需要将所有的数据向后挪动SLDataType大小的字节)
	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->a[end + 1] = psl->a[end];
		--end;
	}
    
	psl->a[0] = x;
    // 头插后,将数组的下标向后移动
	psl->size++;
}

SequentListPopBack(尾删)

void SLPopBack(SL* psl)
{
	assert(psl);

	// 温柔的检查(不会直接报错)
	/*if (psl->size == 0)
	{
		return;
	}*/

	// 暴力的检查
	assert(psl->size > 0);

    // 尾删只需要移动数组的下标(不属于该下标的数据就不是这个数组的数据)
	psl->size--;
}

SequentListPopFront(头删)

void SLPopFront(SL* psl)
{
	assert(psl);
    // 必需保证数组中还有数据
    assert(psl->size > 0);

	int begin = 0;
    // 头删,只需要用后面的数据,覆盖前面的数据
	while (begin < psl->size-1)
	{
		psl->a[begin] = psl->a[begin + 1];
		++begin;
	}
	
    // 数组的下标向前挪动SLDataType大小的字节
    --psl->size;
}

SequentListFind(查找)

int SLFind(SL* psl, SLDataType x)
{
	assert(psl);

	for (int i = 0; i < psl->size; ++i)
	{
		if (psl->a[i] == x)
		{
			return i;
		}
	}

	return -1;
}

SequentListInsert(在指定位置插入)

void SLInsert(SL* psl, size_t pos, SLDataType x)
{
	assert(psl);
    // 插入的位置,必需在数组的范围内
	assert(pos <= psl->size);

    // 插入前需要先检查数组的容量
	SLCheckCapacity(psl);

	// 挪动数据(插入数据后,就需要将插入位置的数据向后挪动)
    //法一
    // 当pos为0时,挪动完最后一个数据时,end为-1;此时判断end是否大于等于pos,会发生整型提升,
    // end会提升为一个无符号整型,则-1存储到内存中,会变为一个很大的正数,导致程序无限循环,
    // 因此需要强转pos的类型为int
	/*
    int end = psl->size - 1;
	while (end >= (int)pos)     
	{
		psl->a[end + 1] = psl->a[end];
		--end;
	}
	*/

    
    // 法二(最优)
	size_t end = psl->size;
	while (end > pos)
	{
		psl->a[end] = psl->a[end-1];
		--end;
	}

	psl->a[pos] = x;
	++psl->size;
}

image-20240402204647739

利用SLInsert来写头插和尾插

// 当pos等于0时,也就是头插

void SLPushFront(SL* psl, SLDataType x)
{
    // 在下标为0的位置插入,也就是头插
    SLInsert(psl, 0, x);
}

// 当pos等于size时,也就是尾插
void SLPushBack(SL* psl, SLDataType x)
{
    // 在下标为size的地方插入,也就是尾插
	SLInsert(psl, psl->size, x);
}

SequentListErase(删除指定位置的值)

void SLErase(SL* psl, size_t pos)
{
	assert(psl);
    // 保证删除的位置在合法范围之内
	assert(pos < psl->size);

	size_t begin = pos;
    // 删除指定位置的数据后,需要挪动数据
	while (begin < psl->size - 1)
	{
		psl->a[begin] = psl->a[begin + 1];
		++begin;
	}

	psl->size--;
}

利用SLErase来实现头删和尾删

// 当pos等于0时,也就是头删

void SLPopFront(SL* psl)
{
    SLErase(psl, 0);
}


// 当pos等于size - 1时,也就是尾删

void SLPopBack(SL* psl)
{
	
	SLErase(psl, psl->size - 1);
}

SequentListModify(修改指定位置的数据)

void SLModify(SL* psl, size_t pos, SLDataType x)
{
	assert(psl);
	assert(pos < psl->size);

	psl->a[pos] = x;
}

顺序表完整代码的超链接

SequentList.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>


// 顺序表的结构
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* a;
	int size;      // 存储数据的个数
	int capacity;  // 存储空间的大小
}SL;


// 顺序表的初始化
void SLInit(SL* psl);


// 顺序表的销毁
void SLDestory(SL* psl);


// 在指定位置插入数据
void SLInsert(SL* psl, size_t pos, SLDataType x);


// 尾插
void SLPushBack(SL* psl, SLDataType x);


// 头插
void SLPushFront(SL* psl, SLDataType x);


// 顺序表的打印
void SLPrint(const SL* psl);


// 查找顺序表中的数,并返回其下标
int SLFind(SL* psl, SLDataType x);


//  删除指定位置的值
void SLErase(SL* psl, size_t pos);


// 头删
void SLPopFront(SL* psl);


// 尾删
void SLPopBack(SL* psl);


// 修改具体位置的数值
void SLModify(SL* psl, size_t pos, SLDataType x);

SequentList.c

#include "SequentList.h"

// 顺序表的初始化
void SLInit(SL* psl)
{
	assert(psl);

	psl->a = NULL;
	psl->capacity = psl->size = 0;
}


// 顺序表的销毁
void SLDestory(SL* psl)
{
	assert(psl);

	free(psl->a);
	psl->a = NULL;
	psl->capacity = psl->size = 0;
}

// 检查容量,并扩容
void SLCheckCapacity(SL* psl)
{
	assert(psl);

	// 检查容量
	if (psl->size == psl->capacity)
	{
		int newCapcity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(psl->a, newCapcity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
			//exit(-1);
		}

		psl->a = tmp;
		psl->capacity = newCapcity;
	}

}

// 在指定位置插入数据
void SLInsert(SL* psl, size_t pos, SLDataType x)
{
	assert(psl);
	assert(pos <= psl->size);

	SLCheckCapacity(psl);

	size_t end = psl->size;
	while (end > pos)
	{
		psl->a[end] = psl->a[end - 1];
		--end;
	}

	psl->a[pos] = x;
	++psl->size;
}


// 尾插
void SLPushBack(SL* psl, SLDataType x)
{
	assert(psl);

	// 当pos等于size时,也就是尾插
	SLInsert(psl, psl->size, x);
}

// 头插
void SLPushFront(SL* psl, SLDataType x)
{
	assert(psl);

	// 当pos等于0时,也就是头插
	SLInsert(psl, 0, x);
}

// 顺序表的打印
void SLPrint(const SL* psl)
{
	assert(psl);
	for (int i = 0; i < psl->size; ++i)
	{
		printf("%d ", psl->a[i]);
	}
	printf("\n");
}

// 查找顺序表中的数,并返回其下标
int SLFind(SL* psl, SLDataType x)
{
	assert(psl);

	for (int i = 0; i < psl->size; ++i)
	{
		if (psl->a[i] == x)
		{
			return i;
		}
	}

	return -1;
}

//  删除指定位置的值
void SLErase(SL* psl, size_t pos)
{
	assert(psl);
	assert(pos < psl->size);

	size_t begin = pos;
	while (begin < psl->size - 1)
	{
		psl->a[begin] = psl->a[begin + 1];
		++begin;
	}

	psl->size--;
}


// 头删
void SLPopFront(SL* psl)
{
	assert(psl);

	// 当pos等于0时,也就是头删
	SLErase(psl, 0);
}


// 尾删
void SLPopBack(SL* psl)
{
	assert(psl);

	// 当pos等于size-1 时,也就是尾删
	SLErase(psl, psl->size - 1);
}


// 修改具体位置的数值
void SLModify(SL* psl, size_t pos, SLDataType x)
{
	assert(psl);
	assert(pos < psl->size);

	psl->a[pos] = x;
}

main.cc

#include "SequentList.h"

enum option
{
	Exit,
    SLPUSHFRONT,   
	SLPOPFRONT,
	SLPUSHBACK,
	SLPOPBACK,
	SLERASE,
	SLINSERT,
	SLFIND,
	SLMODIFY,
	SLPRINT,
};


void menu()
{
	printf("***********************************************\n");
	printf("***** 1.头插  2.头删  3.尾插     4.尾删    *****\n");
	printf("***** 5.删除指定位置数据 6.指定位置插入数据 *****\n");
	printf("***** 7.查找数据        8.修改指定位置数据  *****\n");
	printf("******9.打印数据        0.退出程序         *****\n");
	printf("***********************************************\n");
}


int main()
{
	SL s;         //顺序表
	SLInit(&s);
	int x = 0;
	int pos = 0;
	int input = 0;

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case SLPUSHFRONT: 
			printf("请输入你要插入的数据:>\n");
			scanf("%d", &x);
			SLPushFront(&s, x);
			break;
		case SLPOPFRONT:
			SLPopFront(&s);
			printf("已删除");
			break;
		case SLPUSHBACK:
			printf("请连续输入你要插入的数据,以-1结束\n");
			scanf("%d", &x);
			while (x != -1)
			{
				SLPushBack(&s, x);
				scanf("%d", &x);
			}
			break;
		case SLPOPBACK:
			SLPopBack(&s);
			printf("已删除");
			break;
		case SLERASE:
			printf("请输入要删除的数值:>");
			scanf("%d", &x);
			pos = SLFind(&s, x);
			if (pos != -1)
			{
				SLErase(&s, pos);
			}
			break;
		case SLINSERT:
			printf("请输入插入到哪个数值前面的数并且输入要插入数:>\n");
			int k = 0;   //插入位置的数值
			scanf("%d %d", &k, &x);
			pos = SLFind(&s, k);
			SLInsert(&s, pos, x);
			break;
		case SLFIND:
			printf("请输入查找的数值:>");
			scanf("%d", &x);
			pos = SLFind(&s, x);
			if (pos != -1)
			{
				printf("找到了\n");
			}
			break;
		case SLMODIFY:
			printf("请输入要修改的数值,再输入要修改为哪个数值:>");
			int n = 0;   //要修改的数值
			scanf("%d %d", &n, &x);
			pos = SLFind(&s, n);
			SLModify(&s, pos, x);
			break;
		case SLPRINT:
			SLPrint(&s);
			break;
		case Exit:
			SLDestory(&s);
			break;
		default:
			printf("选择错误\n");
			break;
		}
              
	} while (input);

	return 0;
}

2.3数组相关面试题

1.原地移除数组中所有的元素val,要求时间复杂度为O(N),空间复杂度为O(1)。OJ链接

image-20220824195224694

image-20220824195533347

image-20220824195648193

思路3的分析如下:

// source,destination
// 思路3为最优解,代码如下

int removeElement(int* nums, int numsSize, int val)
{
    int src = 0;
    int dst = 0;
    while(src < numsSize)
    {
        // 当下标src对应的值不是val时,将src对应的数据放到dst对应的位置,src和dst都向后移动
        // 这样就可以保证非val值覆盖掉val
        // 当下标src对应的值是val时,那么只有src向后移动
        if(nums[src] != val)
        {
            nums[dst] = nums[src];
        	src++;
        	dst++;
        }
        else
        {
            src++;
        }
    }
    
    return dst;
}

2.删除排序数组中的重复项。OJ链接

image-20220824202320919

image-20220824202410072

// 代码如下
int removeDuplicates(int* nums, int numsSize)
{
    int src = 0;
    int dst = 0;
    while(src < numsSize)
    {
        // 当nums[src] == nums[dst],说明元素重复
        if(nums[src] == nums[dst])
        {
            src++;
        }
        else
        {
            // 使用后面的元素,覆盖掉dst对应的重复的元素
            dst++;
            nums[dst] = nums[src];
            src++;
        }

    }
    return dst+1;

}

3.合并两个有序数组。OJ链接

image-20220824205724499

image-20220824205927622

image-20220824210022717

// 思路二的代码如下

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
    int end1 = m - 1;
    int end2 = n - 1;
    int i = m + n - 1;
    
    //必须是且的关系,不然一方结束,再比较会越界
    while(end1 >= 0 && end2 >= 0)   
    {
        // 两个数组都是非递减顺序,所有最后一个元素最大
        // 因此,从两个数组最后一个数据开始比较
        // 将数组中较大的元素放在数组1的最末端
        if(nums1[end1] > nums2[end2])
        {
            nums1[i] = nums1[end1];
            end1--;
            i--;
        }
        else
        {
            nums1[i] = nums2[end2];
            end2--;
            i--;
        }
    }

    // end2 结束,nums2数组都拷贝过去了,那么就不用处理了   
    
    // end1 结束,nums1数组都拷贝过去了,那么需要将nums2剩下的数据拷贝过去
    // 此时end2中的数剧都大于end2
    while(end2 >= 0)
    {
        nums1[i] = nums2[end2];
        end2--;
        i--;
    }
}

2.4 顺序表的问题及思考

问题:

  1. 中间/头部的插入删除,时间复杂度为O(N)

  2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

  3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

思考:如何解决以上问题呢?(请关注下一章链表)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值