【数据结构】顺序表-C语言版

顺序表是常用的数据结构,一起来学习顺序表吧


目录

线性表

顺序表及其特点

顺序表的实现

顺序表的应用


线性表

线性表是n个具有相同特性的数据元素的有限序列,是一种实际中广泛使用的数据结构,常见的线性表有顺序表、链表、栈、队列、字符串。

线性表在逻辑上是线性结构,即连续的一条直线,但在物理上不一定连续,线性表在物理上存储时,通常以数组和链式结构的形式存储。

  

顺序表及其特点

数组缺陷:定义数组时必须指定数组大小,但是如果指定的大小不能满足使用空间需求时,就会有问题。

顺序表含义及特点:顺序表本质是数组,是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改,顺序表要从左往右连续存储数据。顺序表的逻辑结构和物理结构都是连续的

2.顺序表分类:顺序表有两种,包括静态顺序表和动态顺序表

(1) 静态顺序表:使用定长数组存储元素。

//顺序表的静态存储
#define N 7
typedef int SLDataType;

typedef struct SeqList
{
	SLDataType array[N];//定长数组
	size_t size;//有效数据的个数
}SeqList;

 (2)动态顺序表:使用动态开辟的数组存储。

typedef struct SeqList
{
	SLDataType* array;//指向动态开辟的数组
	size_t size;//有效数据的个数
	size_t capacity;//容量
}SeqList;

3. 顺序表缺陷:

(1)动态增容有性能消耗。

(2)当头部插入数据时,需要挪动数据

顺序表的实现

 静态顺序表容量是固定的,因此静态顺序表不实用。那么如何实现动态顺序表呢?

18-dynamicSequenceTable.h(结构体定义和方法声明)

#define  _CRT_SECURE_NO_WARNINGS  1
#pragma once

//为int类型重新定义新名称
//如果后续想把SeqList中成员*a的类型改为float或char,不需要把每个使用*a的地方都修改一遍,直接修改typdef定义类型即可
typedef int SeqDataType;

typedef struct SeqList
{
	SeqDataType* a;
	int size;//有效数据的个数
	int capacity;//容量
}SeqList,SEQ;

//内存中管理数据的结构增删改查的接口

//初始化
void SeqListInit(SeqList* seq);

//销毁
void SeqListDestroy(SeqList* seq);

//打印
void SeqListPrint(SeqList* seq);

//扩容
void SeqCheckCapacity(SeqList* seq);

//尾插
void SeqListPushBack(SeqList* seq, SeqDataType x);

//头插
void SeqListPushFront(SeqList* seq, SeqDataType x);

//尾删
void SeqListPopBack(SeqList* seq);

//头删
void SeqListPopFront(SeqList* seq);

//查找
int SeqListFind(SeqList* seq, SeqDataType x);

//某一位置插入数据
void SeqListInsert(SeqList* seq, int pos, SeqDataType x);

//某一位置删除数据
void SeqListErase(SeqList* seq, int pos);

//某一位置删除数据
void SeqListModify(SeqList* seq, int pos,SeqDataType x);

18-dynamicSequenceTable.c(方法实现)

#define  _CRT_SECURE_NO_WARNINGS  1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

#include "18-dynamicSequenceTable.h"

//初始化
void SeqListInit(SeqList* pq)
{
	assert(pq);

	//初始化每个成员变量的值
	pq->a = NULL;
	pq->size = 0;
	pq->capacity = 0;
}

//销毁
void SeqListDestroy(SeqList* pq)
{
	assert(pq);
	free(pq->a);
	pq->a = NULL;
	pq->size = 0;
	pq->capacity = 0;
}

//扩容
void SeqCheckCapacity(SeqList* pq)
{
	//判断是否需要扩容
	if (pq->size == pq->capacity)
	{
		//如果capacity=0,就申请4个a的空间,如果不为0,就申请2倍大的capacity的空间
		//一般扩容都扩容成原来2倍,太小则需要频繁扩容,太大则有可能浪费较多空间
		int newCapacity = pq->capacity == 0 ? 4 : pq->capacity * 2;
		SeqDataType* newA = (SeqDataType*)realloc(pq->a, sizeof(SeqDataType) * newCapacity);
		if (newA == NULL)
		{
			printf("realloc fail\n");
			return;
		}
		pq->a = newA;
		pq->capacity = newCapacity;
	}
}

//尾插
void SeqListPushBack(SeqList* pq, SeqDataType x)
{
	assert(pq);
	
	//判断是否需要扩容
	SeqCheckCapacity(pq);
	
	//把第size个元素值置为x
	pq->a[pq->size] = x;

	//size++
	pq->size++;
}

//头插
void SeqListPushFront(SeqList* pq, SeqDataType x)
{
	assert(pq);

	//判断是否需要扩容
	SeqCheckCapacity(pq);

	//在第一个位置插入数据需要把所有元素向后挪动一个位置,要从最后一个元素开始依次把所有数据拷贝到下一个位置
	int end = pq->size-1;
	while (end>=0)
	{
		//将元素拷贝到该元素下一个位置
		pq->a[end + 1] = pq->a[end];
		end--;
	}

	//将x放在第一个位置
	pq->a[0] = x;

	//size++
	pq->size++;
}

//打印
void SeqListPrint(SeqList* pq)
{
	assert(pq);
	int i = 0;
	for (i = 0; i < pq->size; i++)
	{
		printf("%d ", pq->a[i]);
	}
	printf("\n");
}

//尾删
void SeqListPopBack(SeqList* pq)
{
	assert(pq);
	assert(pq->size>0);
	
	//直接将元素个数-1即可,数据删除不删除无所谓
	pq->size--;
}

//头删
void SeqListPopFront(SeqList* pq)
{
	assert(pq);
	assert(pq->size > 0);
	int begin = 0;
	while (begin < pq->size)
	{
		//删除第一个元素,需要从前往后将其余元素依次拷贝到数组中,如果从后往前拷贝,那么所有元素值都为最后一个元素值
		pq->a[begin] = pq->a[begin + 1];
		begin++;
	}

	//size-1
	pq->size--;
}

//查找
int SeqListFind(SeqList* pq, SeqDataType x)
{
	assert(pq);
	int i = 0;
	for (i = 0; i < pq->size; i++)
	{
		if (pq->a[i] = x)
		{
			return i;
		}
	}

	return -1;
}

//某一位置插入数据
void SeqListInsert(SeqList* pq, int pos, SeqDataType x)
{
	assert(pq);
	assert(pos>0 && pos < pq->size);
	
	//判断是否需要扩容
	SeqCheckCapacity(pq);

	//从后往前拷贝数据
	int i = pq->size-1;
	while (i >= pos)
	{
		pq->a[i + 1] = pq->a[i];
		i--;
	}

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

//某一位置删除数据
void SeqListErase(SeqList* pq, int pos)
{
	assert(pq);
	assert(pos > 0 && pos < pq->size);

	int i = pos;
	while (i < pq->size)
	{
		pq->a[i] = pq->a[i + 1];
		i++;
	}
	pq->size--;
}

//修改某一位置数据
void SeqListModify(SeqList* pq, int pos, SeqDataType x)
{
	assert(pq);
	assert(pos >= 0 && pos < pq->size);

	pq->a[pos] = x;
}

头插需要从后向前拷贝的原因:如果从前向后对数组依次拷贝,先拷贝1,最后拷贝5,就会把所有数据都变成小标为0的元素,因此要从后向前拷贝,先拷贝5,最后拷贝1。

 18-test.c(测试、方法调用)

#define  _CRT_SECURE_NO_WARNINGS  1
#include<stdio.h>
#include "18-dynamicSequenceTable.h"

TestSEQList1()
{
	SeqList s = {NULL,0,0};

	//想要修改结构体变量,必须传结构体的地址
	//因此SeqListInit的实参是结构体地址,形参应该是指针,用来接收结构体地址,其他方法同理
	SeqListInit(&s);

	//尾插 1 2 3 4 5
	SeqListPushBack(&s, 1);
	SeqListPushBack(&s, 2);
	SeqListPushBack(&s, 3);
	SeqListPushBack(&s, 4);
	SeqListPushBack(&s, 5);
	
	//头插 0 0 0 0 0
	SeqListPushFront(&s, 0);
	SeqListPushFront(&s, 0);
	SeqListPushFront(&s, 0);
	SeqListPushFront(&s, 0);
	SeqListPushFront(&s, 0);
	SeqListPrint(&s);

	//头删一个元素
	SeqListPopFront(&s);
	SeqListPrint(&s);

	//尾删一个元素
	SeqListPopBack(&s);
	SeqListPrint(&s);

	//指定位置插入一个元素
	SeqListInsert(&s,2,6);
	SeqListPrint(&s);

	//指定位置删除一个元素
	SeqListErase(&s, 2);
	SeqListPrint(&s);

    //修改指定位置元素
	SeqListModify(&s, 0, -1);
	SeqListPrint(&s);

	//销毁顺序表
	SeqListDestroy(&s);

}

int main()
{
	TestSEQList1();
	return 0;
}

 执行结果:

顺序表的应用

1.力扣网- 数组形式的整数加法

 对于非负整数 X 而言,X 的数组形式是每位数字按从左到右的顺序形成的数组。例如,如果 X = 1231,那么其数组形式为 [1,2,3,1]。给定非负整数 X 的数组形式 A,返回整数 X+K 的数组形式。示例:

输入:A = [9,9,9,9,9,9,9,9,9,9], K = 1
输出:[1,0,0,0,0,0,0,0,0,0,0]
解释:9999999999 + 1 = 10000000000

分析:

(1)不确定数组和数字相加的结果会不会产生进位,因此要用malloc为新数组分配空间,大小为数组和数字较大位数值+1。

(2)进位如何解决?将数组和数字对应位及进位相加的和如果比10大,那么该位结果就要-10,进位置1。

(3)对于(2)中的结果如何存放?如果将结果在新数组中从最右侧往最左侧存放,那么有可能相加的最终结果最高位没有产生进位,这就会导致新数组最高位为0,还需要将其他位顺次向前挪,如下图左图所示。因此应从最左侧往最右侧存放,记录新数组实际使用的长度,计算完毕再将数组逆序。

#define  _CRT_SECURE_NO_WARNINGS  1
#include<stdio.h>
#include<stdlib.h>
int* addToArrayForm(int* A, int ASize, int k, int* returnSize) 
{
	int kSize = 0;
	int num = k;
	
	//计算数字k的位数
	while (num)
	{
		num /= 10;
		kSize++;
	}

	//新数组大小为:max{数组长度,数字位数}+1,+1的原因是并不知道相加的最高位会不会有进位
	int len = ASize > kSize ? ASize + 1 : kSize + 1;
	
	//为新数组malloc动态开辟空间
	int* retArr = (int*)malloc(sizeof(int) * len);
	
	if (retArr == NULL)
	{
		return NULL;
	}
	
	int Ai = ASize - 1;//数组下标
	int Ki = 0;//数字从低到高第i位
	int next = 0;//进位
	int reti = 0;//新数组最终长度
	
	//只要数组和数字有一个没走完就进while循环
	while (Ai >= 0 || Ki < kSize)
	{
		int aval = 0;
		
		if (Ai >= 0)
		{
			aval = A[Ai--];//取数组最右端一位
		}


		int kval = k % 10;//取数字最低位
		k /= 10;
		Ki++;

		//相加和
		int ret = aval + kval + next; 

		//判断相加和是否产生进位
		if (ret >= 10)
		{
			next = 1;
			ret -= 10;
		}
		else
		{
			next = 0;
		}

		//注意此时将结果在新数组中按照从左到右的顺序存储,因此执行结束后,新数组需要逆置
		retArr[reti++] = ret;
	}

	//最高位相加的结果如果进位是1,那么还需将1存储在新数组最高位
	if (next == 1)
	{
		retArr[reti++] = 1;
	}
	
	//逆置新数组
	int begin = 0, end = reti - 1;
	while (begin < end)
	{
		int temp = retArr[begin];
		retArr[begin] = retArr[end];
		retArr[end] = temp;
		begin++;
		end--;
	}

	*returnSize = reti;
	return retArr;
	
}
int main()
{
	int array[] = { 9,9,9,9,9 };
	int len = sizeof(array) / sizeof(array[0]);
	int* p = array;
	int number = 999;
	int newlen = 0;
	
	int *p1 = addToArrayForm(array, len, number, &newlen);
	
	int i = 0;
	for (i = 0; i < newlen; i++)
	{
		printf("%d ", *(p1 + i));
	}
	return 0;
}

 执行结果如下:

2.力扣网- 合并两个有序数组

 给你两个按非递减顺序排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你合并 nums2 到 nums1 中,使合并后的数组同样按非递减顺序排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

分析:

(1)nums1长度为m+n,无需开辟新数组。

(2)从数组左侧开始比较还是从数组右侧开始比较?如果从左侧开始比较,如果nums2的数据值<nums1的数据值,那么nums2的数据值放在nums1的对应位上,nums1的数据就会被覆盖,找不回来了,如下图所示。因此要从数组右侧向左开始比较。 

 (3)什么时候比较结束?nums1和nums2有一个走完就不用比较了,因此进入循环的判断条件是nums1和nums2的下标同时要>=0,当有一个不满足条件时,表明有一个数组走完了。此时如果是nums1走完了,那么就要把nums1剩下的数据直接拷贝到nums1前面剩下的几个位置中;如果是nums2走完了,那么就不用动,因为这就说明nums1剩下的没走的那几个数据比nums2的数据都小。

#define  _CRT_SECURE_NO_WARNINGS  1
#include<stdio.h>
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) 
{
	int end1 = m - 1;
	int end2 = n - 1;
	int end = m + n - 1;

	//从右侧开始比较nums1和nums2的数据值,将较大者放在nums1数组的最右侧
	while (end1 >= 0 && end2 >= 0)
	{
		if (nums1[end1] > nums2[end2])
		{
			nums1[end--] = nums1[end1--];
		}
		else
		{
			nums1[end--] = nums2[end2--];
		}
		 
	}

	//有一个数组走完,如果是nums2没走完,需要将nums2剩余数据拷贝到nums1前面位置中
	//如果是nums1没走完,不用动,因为nums1剩下的数据是最小的,本身就在nums1中
	while (end2 >= 0)
	{
		nums1[end--] = nums2[end2--];
	}
}

int main()
{
	int arr1[] = { 2,3,6,0,0,0,0};
	int len1 = sizeof(arr1) / sizeof(arr1[0]);

	int arr2[] = {1,2,5,7};
	int len2 = sizeof(arr2) / sizeof(arr2[0]);
	
	merge(arr1, len1, 3, arr2, len2, len2);
	
	int i = 0;
	for (i = 0; i < len1; i++)
	{
		printf("%d ", arr1[i]);
	}
}

执行结果:

3力扣网-轮转数组

给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。要求空间复杂度O(1)

示例:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]

分析:有2种解法可以让空间复杂度为O(1):

(1)先实现旋转一次,再对旋转一次的方法执行旋转k次

#define  _CRT_SECURE_NO_WARNINGS  1
#include<stdio.h>
void rotate(int* nums, int numsSize,int k)
{

	int i = 0;
	int end = numsSize;

	//旋转次数>数组长度,旋转次数需要对数组长度取模
	k %= numsSize;
	for (i = 0; i < k; i++)
	{
		//将最后一个元素保存下来
		int temp = nums[numsSize - 1];
		int j = 0;

		//一次向右旋转,将其余元素依次向后挪动
		for (j = numsSize - 2; j >= 0; j--)
		{
			nums[j + 1] = nums[j];
		}

		//把最后一个元素放在数组首元素的位置
		nums[0] = temp;
	}

}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7 };
	int len = sizeof(arr) / sizeof(arr[0]);
	
	int k = 6;
	
	rotate(arr, len, k);

	int i = 0;
	for (i = 0; i < len; i++)
	{
		printf("%d ", arr[i]);
	}
	
	return 0;
}

 执行结果:

 (2)三步翻转法:将前k个元素进行翻转,再将后面len-k个元素进行翻转,最后整体翻转。

#define  _CRT_SECURE_NO_WARNINGS  1
#include<stdio.h>

//实现从起始位置到结束位置的翻转
void reverse(int* nums, int begin, int end)
{
	//起始位置与结束位置逐一对调
	while (begin < end)
	{
		int temp = nums[begin];
		nums[begin] = nums[end];
		nums[end] = temp;
		begin++;
		end--;
	}
}

void rotate(int* nums, int numsSize, int k) 
{
	reverse(nums, 0, k - 1);//翻转前k个元素
	reverse(nums, k, numsSize - 1);//翻转后len-k个元素
	reverse(nums, 0, numsSize - 1);//整体翻转
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7 };
	int len = sizeof(arr) / sizeof(arr[0]);

	int k = 3;

	rotate(arr, len, k);
	int i = 0;
	for (i = 0; i < len; i++)
	{
		printf("%d ", arr[i]);
	}

	return 0;
}

执行结果:

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值