【数据结构】顺序表

目录

前置知识补充

memset

动态内存分配(malloc realloc free)

 assert

顺序表程序的实现

顺序表的基本概述

顺序表程序的基本思路

SeqList.h

SeqList.c

test.c

顺序表oj练习题

移除元素

合并两个有序数组


前置知识补充

memset

void * memset ( void * ptr, int value, size_t num );

Fill block of memory()

Sets the first num bytes of the block of memory pointed by ptr to the specified value (interpreted as an unsigned char).

memset()函数被包含在string.h的库中;

将ptr指向的内存空间的前num个字节设置为指定值。

/* memset example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] = "almost every programmer should know memset!";
  memset (str,'-',6);
  puts (str);
  return 0;
}

//输出------ every programmer should know memset!

动态内存分配(malloc realloc free)

malloc是动态内存分配函数,用于申请一块连续指定大小的内存块区域以void*类型返回分配的内存区域地址

关于malloc所开辟空间类型:malloc只开辟空间,不进行类型检查,只是在使用的时候进行类型的强转
举个例子:‘我’开辟你所需要大小的字节大小空间,至于怎么使用是你的事
mallo函数返回的实际是一个无类型指针,必须在其前面加上指针类型强制转换才可以使用
指针自身 = (指针类型*)malloc(sizeof(指针类型)*数据数量)
在使用malloc函数之前我们一定要计算字节数,malloc开辟的是用户所需求的字节数大小的空间。
如果多次申请空间那么系统是如何做到空间的不重复使用呢?
在使用malloc开辟一段空间之后,系统会在这段空间之前做一个标记(0或1),当malloc函数开辟空间如果遇到标记为0就在此开辟,如果为1说明此空间正在被使用。
————————————————
原文链接:https://blog.csdn.net/qq_42565910/article/details/90346236

#include <stdio.h>
#include <stdlib.h>
int *p = NULL;
int n = 10;
p = (int *)malloc(sizeof(int)*n);
//开辟了一个头指针为p,字节数为4*10的内存空间

语法:指针名=(数据类型*)realloc(要改变内存大小的指针名,新的大小)。

      //新的大小若小于原来的大小,原数据的末尾可能丢失(被其他使用内存的数据覆盖等)

头文件:#include <stdlib.h> 有些编译器需要#include <malloc.h>,在TC2.0中可以使用alloc.h头文件

功能:先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。

返回值:如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。

 realloc() 用法详解_小可爱amour的博客-CSDN博客_realloc用法

#include<stdio.h>
#include<stdlib.h>
#define N 10000
int main()
{
	int i;
	int *pn=(int *)malloc(5*sizeof(int));
	printf("%p\n",pn);
	for(i=0;i<5;i++)
		pn[i] = i+1;
 
	printf("%d\n",sizeof(int));
	int *pnn=(int *)realloc(pn, N*sizeof(int));
	printf("%p\n",pnn);
	for(i=0;i<5;i++)
	printf("%3d",pnn[i]);
	
	//for(i=0;i<5;i++)
	//printf("%3d",pn[i]);
	printf("\n");
	//free(pn);
	free(pnn);
	return 0;
}
void free (void* ptr);

Deallocate memory block

A block of memory previously allocated by a call to malloccalloc or realloc is deallocated, making it available again for further allocations.

If ptr does not point to a block of memory allocated with the above functions, it causes undefined behavior.

If ptr is a null pointer, the function does nothing.

Notice that this function does not change the value of ptr itself, hence it still points to the same (now invalid) location.

free()函数的作用是对动态分配的内存进行释放

free函数只是将参数指针指向的内存归还给操作系统,并不会把参数指针置NULL,为了以后访问到被操作系统重新分配后的错误数据,所以在调用free之后,通常需要手动将指针置NULL

#include <stdlib.h>     /* malloc, calloc, realloc, free */

int main ()
{
  int * buffer1, * buffer2, * buffer3;
  buffer1 = (int*) malloc (100*sizeof(int));
  buffer2 = (int*) calloc (100,sizeof(int));
  buffer3 = (int*) realloc (buffer2,500*sizeof(int));
  free (buffer1);
  free (buffer3);
  return 0;
}

 assert

void assert (int expression);

Evaluate assertion

If the argument expression of this macro with functional form compares equal to zero (i.e., the expression is false), a message is written to the standard error device and abort is called, terminating the program execution.

assert()函数被包含在assert.h 的库中;

判断expression表达式是否正确,如果不正确,则报错终止程序。

#include <stdio.h>      /* printf */
#include <assert.h>     /* assert */

void print_number(int* myInt) {
  assert (myInt!=NULL);
  printf ("%d\n",*myInt);
}

int main ()
{
  int a=10;
  int * b = NULL;
  int * c = NULL;

  b=&a;

  print_number (b);
  print_number (c);

  return 0;
}

//10
//Assertion failed!

顺序表程序的实现

顺序表的基本概述

顺序表是一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。顺序表一般可以实现增删查改功能。

顺序表一般分为两类:

1.静态顺序表,使用memset()定义数组的长度,所以数组长度有限,数组长度不好确定,开大了浪费,开小了不够。

2.动态顺序表,使用malloc()动态分配内存,好处:可以根据数据的大小灵活的改变顺序表的大小

顺序表程序的基本思路

SeqList.h

头文件的包含、函数的申明、常量的定义和结构体的定义

结构体的定义:

定义一个头指针/数组

定义size:有效数据的个数,顺序表的大小是由size的大小确定的

定义capacity(动态顺序表特有):控制动态顺序表的容量

#pragma once


//头文件的包含
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<malloc.h>
#include<assert.h>

//常量的定义
//#define MAX_SIZE 10    
//静态顺序表:给少了不够用,给多了浪费

typedef int SQDataType;  //数据类型用typedef定义

typedef struct SeqList
{
	SQDataType*a;          //开辟内存空间
	int size;              //有效数据的个数
	int capacity;          //容量
}SL;
//上述的操作就是将struct SeqList等价成SL,简化代码
//typedef struct SeqList;和上述操作等价

//函数的申明
void SeqListInit(SL*ps);
//头插和尾插,注意参数x是要插入的元素,元素的数据类型和原顺序的数据类型一致
void SeqListPushBack(SL* ps, SQDataType x);
void SeqListPushFront(SL* ps, SQDataType x);
//头删 尾删
void SeqListPopBack(SL* ps);
void SeqListPopFront(SL* ps);

//在pos处插入或删除数据
void SeqListInsert(SL* ps, int pos,SQDataType x);
void SeqListErase(SL* ps, int pos);

//打印顺序表
void SeqListPrint(SL* ps);

//销毁顺序表
void SeqListDestory(SL* ps);

//查找数据
int SeqListFind(SL* ps, SQDataType x);

//修改数据
void SeqListModify(SL* ps, int pos, SQDataType x);

SeqList.c

增删查改,顺序表的定义,顺序表的销毁


SeqListInit

静态顺序表:

void SeqListInit(SL*ps)
{
	memset(ps->a, 0, sizeof(SQDataType) * MAX_SIZE);
	//将s1.a这个数组前sizeof(SQDataType) * MAX_SIZE字节的值设置成0
	//sizeof(SQDataType)计算出数组中每个元素所占的字节数
	//然后*MAX_SIZE就是整个数组所占的空间
	ps->size = 0;

}

动态顺序表:

void SeqListInit(SL* ps)
{
	ps->a=NULL;
	ps->capacity = 0;
	ps->size = 0;
}

SeqListCapacityCheck

检查顺序表的容量,如果容量不够,则需要增容

void SeqListCapacityCheck(SL* ps)
{
	if (ps->size >= ps->capacity)
	{
		int newcapacity = 0;
		if (ps->capacity == 0)
		{
			newcapacity = 4;//如果初始容量为0,则赋初值4
		}
		else
		{
			newcapacity = 2 * ps->capacity;
		}
		SQDataType* tmp = (SQDataType*)realloc(ps->a, sizeof(SQDataType)*newcapacity);
		if (tmp == NULL)
		{
			printf("realloc fail!\n");
			exit(-1);//退出程序
		}
		else
		{
			ps->a = tmp;
			ps->capacity = newcapacity;	//增加capacity的值
		}

	}
}

SeqListPushBack / SeqListPushFront

1.确定顺序表是否需要增容(调用SeqListCapacity函数)

2.开始插入

例:ps->size=5      插入的数据为x

0123400000

x012340000

注意由于顺序表是依次存储数据的,在进行头插法时,应该先从后往前将数据依次向后移位,再插入数据

3.扩大size的值

//尾插法
void SeqListPushBack(SL* ps, SQDataType x)
{
	SeqListCapacityCheck(ps);
	ps->a[ps->size] = x;
	ps->size++;
}

//头插法
void SeqListPushFront(SL* ps, SQDataType x)
{
	SeqListCapacityCheck(ps);
	int end = ps->size - 1;
	while (end>=0)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[0] = x;
	ps->size++;
}

SeqListPopback / SeqListPopFront

1.应用assert函数,判断size的值是否可以进行删除

2.删除,头删法的思路和头插法基本一致

3.减小size的值

//尾删法
void SeqListPopBack(SL* ps)
{
	assert(ps->size > 0);//如果size<=0,则程序报错
	ps->size--;//顺序表内判断数据是否存在,是靠识别size这个参数的大小实现的。
}

//头删法
void SeqListPopFront(SL* ps)
{
	assert(ps->size > 0);
	int start = 1;
	while (start <= ps->size - 1)
	{
		ps->a[start - 1] = ps->a[start];
		start++;
	}
	ps->size--;
}

SeqListPrint

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

SeqListInsert

1.使用assert()函数,确定插入位置的合法性

2.调用SeqListCapacityCheck(),确定是否需要扩容

3.插入:和头查尾插一致

//插入数据
void SeqListInsert(SL* ps, int pos,SQDataType x)
{
	assert(pos < ps->size);//顺序表的数据是依次存储的 pos不能大于size的大小
	SeqListCapacityCheck(ps);
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;
	ps->size++;
}

SeqListErase

void SeqListErase(SL* ps, int pos)
{
	assert(pos < ps->size);
	int start = pos + 1;
	while (start <= ps->size - 1)
	{
		ps->a[start - 1] = ps->a[start];
		start++;
	}
	ps->size--;
}

SeqListFind

如果查找成功,则输出位置

如果不成功,则输出-1

int SeqListFind(SL* ps, SQDataType x)
{
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (x == ps->a[i])
		{
			return i;
		}
	}
	if (i == ps->size)
	{
		return -1;
	}
}

SeqListModify

void SeqListModify(SL* ps, int pos, SQDataType x)
{
	assert(pos < ps->size);
	ps->a[pos] = x;
}

SeqListDestory

释放内存空间,并将头指针置空,以免出现野指针

void SeqListDestory(SL* ps)
{
	//释放空间,如果没有释放空间,会导致内存泄露
	free(ps->a);
	ps->a = NULL;//防止出现野指针
	ps->capacity = ps->size = 0;
}

test.c

编写一个菜单,实现顺序表中多个接口函数的功能汇总。

#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"

void menu()
{
	printf("******************************************************************\n");
	printf("*********************** 1. 头插    2. 尾插 ***********************\n");
	printf("*********************** 3. 头删    4. 尾删 ***********************\n");
	printf("*********************** 5. 打印    6. 查找 ***********************\n");
	printf("*********************** 7. 插入    8. 删除 ***********************\n");
	printf("*********************** 9. 修改   -1. 退出 ***********************\n");
	printf("******************************************************************\n");

}


int main()
{
	//TestSeqList1();
	//TestSeqList2();
	int option = 0;
	int input = 0;
	int pos = 0;
	SL sl;
	SeqListInit(&sl);
	do
	{
		menu();
		scanf("%d", &option);
		system("cls");
		switch (option)
		{
		case 1:
			printf("请输入你要插入的数据\n");
			scanf("%d", &input);
			SeqListPushFront(&sl, input);
			system("cls");
			break;
		case 2:
			printf("请输入你要插入的数据\n");
			scanf("%d", &input);
			SeqListPushBack(&sl, input);
			system("cls");
			break;
		case 3:
			SeqListPopFront(&sl);
			system("cls");
			break;
		case 4:
			SeqListPopBack(&sl);
			system("cls");
			break;
		case 5:
			SeqListPrint(&sl);
			int tmp = 0;
			printf("按1返回菜单\n");
			scanf("%d", &tmp);
			system("cls");
			break;
		case 6:
			printf("请输入你要查找的值\n");
			scanf("%d", &input);
			pos = SeqListFind(&sl, input);
			if (pos == -1)
			{
				printf("查无此数\n");
			}
			else
			{
				printf("%d\n", pos);
			}
			system("cls");
			break;
		case 7:
			printf("请输入你要插入的数值和对应的位置\n");
			scanf("%d %d", &input, &pos);
			SeqListInsert(&sl, pos, input);
			system("cls");
			break;
		case 8:
			printf("请输入你要删除的数值的位置\n");
			scanf("%d",&pos);
			SeqListErase(&sl, pos);
			system("cls");
			break;
		case 9:
			printf("请输入你要修改的数值和位置\n");
			scanf("%d %d", &input, &pos);
			SeqListModify(&sl, pos, input);
			system("cls");
			break;
		case -1:
			break;
		default:
			printf("输入非法,请重新输入\n");
			break;
		}
	} while (option != -1);
	SeqListDestory(&sl);
	return 0;
}

顺序表oj练习题

移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/remove-element

思路:

思路一:遍历这个数组,如果和val值相等,则进行删除操作。时间复杂度为O(N^2)

思路二:以空间换时间,创建一个相同大小的新数组,对原数组进行遍历,如果和val值不同,则将这个值赋给新数组,再将新数组拷贝给原数组。时间复杂度为O(N) 空间复杂度为(N)

思路三:双指针法

val=2 nums Size=5

↓    src
02523
↑   dst

src相当于原数组,dst相当于新数组

src开始遍历  1,如果src指向的值和val相等,不要给新数组赋值,所以dst不动,src++

                     2,如果src指向的值和val不相等,将该值赋给新数组,所以把src指向的值赋给dst指                            向的值,src++,val++

int removeElement(int* nums, int numsSize, int val){
    int*src = nums;
    int*dst = nums;
    while(src-nums <= numsSize-1)
    {
        if(val == *src)
        {
            src++;
        }
        else
        {
            *dst = *src;
            src++;
            dst++;
        }
    }
    return dst - nums;
}

合并两个有序数组

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

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

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

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/merge-sorted-array

思路一:新建一个数组nums3:num1和num2两个数组同时开始遍历,如果num1中的数小,则                    将num1的数放入num3中;如果num2中的数小,则把num2的数放入num3中

思路二:双指针法(但是从后往前遍历),max{src指向的值,dst指向的值}放入end指向的位置

num1:

↓src↓end
01223000

num2:

↓dst
156

如果num1中有数值剩余,则完成

如果num2中有数值剩余,则将num2中剩余的数值拷贝到num1的前端

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
    int src = m - 1;
    int dst = n - 1;
    int end = m + n - 1;
    while(src >= 0 && dst >= 0)
    {
        if(nums1[src] > nums2[dst])
        {
            nums1[end] = nums1[src];
            src--;
            end--;
        }
        else
        {
            nums1[end] = nums2[dst];
            end--;
            dst--;
        }
    } 
    while(dst >= 0)
    {
        nums1[end] = nums2[dst];
        dst--;
        end--;
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值