数据结构深度剖析顺序表:结构、扩容与增删查改全解析

文章目录

  • 1、顺序表的概念及结构
    • [1.1 线性表](#1.1 线性表)
    • [1.2 顺序表的分类](#1.2 顺序表的分类)
  • [2. 动态顺序表的实现](#2. 动态顺序表的实现)
    • [2.1 Seqlist.h 文件:](#2.1 Seqlist.h 文件:)
    • [2.2 Seqlist.c 文件:](#2.2 Seqlist.c 文件:)
    • [2.3 test.c 文件:](#2.3 test.c 文件:)
  • [3. 顺序表的应用](#3. 顺序表的应用)
    • [3.1 基于动态顺序表实现通讯录项目](#3.1 基于动态顺序表实现通讯录项目)
      • [1. Contact.h文件](#1. Contact.h文件)
      • [2. Contact.c文件](#2. Contact.c文件)
      • [3. Seqlist.h文件](#3. Seqlist.h文件)
      • [4. Seqlist.c文件](#4. Seqlist.c文件)
      • [5. test.c文件](#5. test.c文件)
  • [4. 顺序表经典算法题](#4. 顺序表经典算法题)
  • [5. 顺序表的问题与思考](#5. 顺序表的问题与思考)

1、顺序表的概念及结构

1.1 线性表

顺序表是线性表的一种

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

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

1.2 顺序表的分类

首先明确一下顺序表和数组的区别:

**顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口 **

1. 静态顺序表:

使用定长的数组存储元素

c 复制代码
struct SeqList
{
	int arr[100];//定长的数组
	int size; //顺序表当前的有效数据个数
};

静态顺序表的优点是实现极简,不用 malloc/free
无需考虑扩容、内存释放问题。缺点是容量死板,不能灵活变化开小了不够用,开大了浪费内存受栈空间限制,不适合存大量数据。这个时候就引入了动态顺序表优点是容量可动态伸缩,灵活自由内存按需分配,不浪费空间堆区空间大,适合大批量数据存储,所以在实际的开发中静态的顺序表几乎不会使用。

2. 动态顺序表:

按需申请空间

c 复制代码
struct Seqlist
{
	int *arr;
	int size;//有效的数据个数
	int capacity;/// 当前总容量

};

2. 动态顺序表的实现

2.1 Seqlist.h 文件:

实现顺序表的结构以及声明顺序表的方法

c 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
/*头文件中:
1.顺序表的结构
2.声明顺序表的方法
*/


//1.定义顺序表的结构

//静态顺序表

//#define N 100
//
//struct Seqlist
//{
//	int arr[N];//定长的数组,最多存放100个int
//	int szie;//当前实际存储的有效数据个数
//};


//推荐使用动态顺序表

typedef int SLDataType;//方便后续统一的类型替换
//动态顺序表
typedef struct Seqlist
{
	SLDataType* arr;//指向动态分配的数组
	int size;//记录有效的数据个数(最后一个有效数据的下一个位置)
	int capacity;//记录动态内存申请到了多大的空间
}SL;

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

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

//顺序表的打印
void SLPrint(SL s);

//尾部插⼊SLPushBack
void SLPushBack(SL* ps, SLDataType x);

//头部插⼊SLPushFront
void SLPushFront(SL* ps, SLDataType x);

//头部插⼊删除SLPopFront
void SLPopFront(SL* ps);

//尾部插⼊删除SLPushBack
void SLPopBack(SL* ps);

//指定位置的插⼊/删除数据
void SLInsert(SL * ps, int pos, SLDataType x);

void SLErase(SL* ps, int pos);

//顺序表的查找
int  SLFind(SL* ps, SLDataType x);

2.2 Seqlist.c 文件:

实现顺序表的具体方法

c 复制代码
#define _CRT_SECURE_NO_WARNINGS

/*
.c文件中:
实现顺序表的具体方法*/


//顺序表初始化

#include "Seqlist.h"
void SLInit(SL*  ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

// 顺序表的销毁
void SLDestroy(SL * ps)
{
	if (ps->arr)//等价于if(ps->arr != NULL)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;

}


//检查判断头插和尾插空间容量够不够的函数
void SLCheckCapacity(SL* ps)
{
	//注意:插入之前先看空间够不够
	if (ps->capacity == ps->size)//空间不够进入函数
	{
		//申请空间(用那个内存管理函数呢?)
		//malloc calloc realloc ?
		//因为涉及增容的需求其实是使用realloc


		//问题又来了申请多大的空间呢?/一次增容多大
		//其实增容通常来说是成倍数的增加,一般是2到3倍
		//因为当原内存空间没有足够的连续空间时realloc会搬迁内容到新的内存空间一次一次地去增容过于频繁会导致程序性能低下

		//因为capacity初始化的时候是0所以增容前先判断一下capacity空间是否为0
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		//如果 ps->capacity 等于 0,则表达式的值为 4
		//否则(即容量不为0),表达式的值为 2 * ps->capacity(当前容量的两倍)。


		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
		if (tmp == NULL)//空间申请失败
		{
			perror("realloc fail");
			exit(1);//退出程序
		}
		//确定空间申请成功然后给到arr
		ps->arr = tmp;
		ps->capacity = newCapacity;


	}
}



//尾部插⼊SLPushBack
void SLPushBack(SL* ps, SLDataType x)
{
	//if (ps == NULL)//防止用户输入意想不到的内容加强代码的健壮性
	//{
	//	return;//此处也可以使用断言
	//}
	assert(ps);



	//插入之前先判断空间够不够
	SLCheckCapacity(ps);



	
	//前面所有完成后再插入数据
	ps->arr[ps->size++] = x;
	//插入数据到size位置后再让size++
		
	//ps->arr[ps->size] = x;
	//++ps->size;
	//另一种写法
}





//头部插入SLPushFront
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);



	//先判断空间够不够
	SLCheckCapacity(ps);

	//先让顺序表中已有的数据整体往后挪动一位
	//怎么挪动?
	//首先size是指向最后一个有效数据的下一个位置
	//然后让size前的这个数据移动到size的位置前面的数据依次往后移
	for (int i = ps->size; i>0 ; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
		//最后当arr[1]=arr[0]时循环结束
		//结束条件就是i>0

		//注意:当写循环时不知道怎么写结束条件时
		//可以先写下去再反推结束条件
	}
	//这个时候arr[0]空出来了可以开始头插了
	ps->arr[0] = x;
	ps->size++;
}



//顺序表的打印
void SLPrint(SL s)
{
	for (int i = 0; i < s.size; i++)
	{
		printf("%d ", s.arr[i]);

	}
	printf("\n");
}



//尾部插⼊删除SLPushBack
void SLPopBack(SL* ps)
{
	assert(ps);
	

	//另一种情况顺序表为空此时size处下标为0就不能减减因为没有下标为-1的说法
	//这时就需要判断顺序表是否为空为空就不能执行删除操作
	assert(ps->size);

	//走到这里顺序表不为空
	--ps->size; //删完数据,数据要少一个,size一定要减减
}




//头部插⼊删除SLPopFront


//删除下标为0处的元素数据
//然后数据整体向前挪动一位
void SLPopFront(SL* ps)
{
	assert(ps);

	assert(ps->size);

	//数据整体向前挪动一位
	for (int i = 0; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
		//最后一次是把size-1处的数据移动到size-2处
		//所以循环的结束条件是i < ps->size-1

	}
	ps->size--;

}





//指定位置的插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	//SL* ps是指向顺序表结构体的指针。
	
	//SLDataType x要插入的数据元素。

	//int pos插入位置的下标指定新元素要插入到顺序表中的哪个位置之前
	/*若 pos = 0,则新元素插入到表头(第一个元素之前)
	若 pos = size,则新元素插入到表尾(最后一个元素之后,此时相当于尾插)。
	通常要求 pos 的取值范围是 [0, size],否则为无效位置。*/

	assert(ps);
	assert(pos >= 0 && pos <= ps->size);

	//插入之前先看空间够不够
	SLCheckCapacity(ps);
	//让pos及之后的数据整体后移一位把pos空出来
	for (int i = ps->size; i > pos ; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;

	ps->size++;
}





//指定位置的删除数据
void SLErase(SL* ps, int pos)
{
	//SL* ps指向顺序表结构体的指针,用于访问和修改顺序表中的数据。

	//int pos要删除的元素在顺序表中的下标(从 0 开始)。


	assert(ps);
	assert(pos >= 0 && pos < ps->size);

	for (int i = pos; i < ps->size - 1; i++)//i < ps->size - 1 这个条件用于控制循环的范围,避免数组越界。
	{
		ps->arr[i] = ps->arr[i + 1];

	}
	ps->size--;
}





//顺序表的查找
int  SLFind(SL* ps, SLDataType x)
{
	//SL* ps:指向顺序表结构体的指针,用于访问顺序表中的数据
	//SLDataType x:要查找的数据元素,其类型与顺序表中存储的数据类型一致。


	assert(ps);

	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			//找到了
			return i;
		}
	}
	//没有找到
	return -1;
}

2.3 test.c 文件:

测试代码逻辑和功能

c 复制代码
#define _CRT_SECURE_NO_WARNINGS


//测试文件
#include "Seqlist.h"

void SLTest01()
{
	SL sl;
	//初始化
	SLInit(&sl);
	//增删查改操作
	//测试尾插
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	//打印尾插
	SLPrint(sl);


	//测试头插
	//SLPushFront(&sl, 5);
	//SLPushFront(&sl, 6);
	//打印头插
	//SLPrint(sl);


	//测试头删
	//SLPopFront(&sl);
	//SLPrint(sl);
	//SLPopFront(&sl);
	//SLPrint(sl);
	//SLPopFront(&sl);
	//SLPrint(sl);
	//SLPopFront(&sl);
	//SLPrint(sl);


	//测试尾删
	SLPopBack(&sl);
	SLPrint(sl);
	SLPopBack(&sl);
	SLPrint(sl);
	SLPopBack(&sl);
	SLPrint(sl);
	SLPopBack(&sl);
	SLPrint(sl);



	// 顺序表的销毁
	SLDestroy(&sl);
}
void SLTest02()
{
	SL sl;
	SLInit(&sl);
	//增删查改操作
	//测试尾插
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);	


	//打印尾插
	SLPrint(sl);



	//测试在指定位置插入数据
	//SLInsert(&sl, 1, 99);
	////SLInsert(&sl, sl.size, 88);
	//SLPrint(sl);



	//测试在指定位置删除数据
	//SLErase(&sl, 2);
	//SLPrint(sl);


	
	//测试顺序表的查找
	int find = SLFind(&sl, 4);
	if (find < 0)
	{
		printf("没有找到!\n");
	}
	else
	{
		printf("找到了!下标是:%d\n",find);
	}




	SLDestroy(&sl);
}

int main()
{
	//SLTest01();
	SLTest02();
	return 0;
}

3. 顺序表的应用

3.1 基于动态顺序表实现通讯录项目

基本功能:

1)至少能够存储100个人的通讯信息
2)能够保存用户信息:名字、性别、年龄、电话、地址等
3)增加联系人信息
4)删除指定联系人
5)查找制定联系人
6)修改指定联系人
7)显示联系人信息

1. Contact.h文件

c 复制代码
#pragma once

#define NAME_MAX 20
#define GENDER_MAX 10 
#define TEL_MAX 20
#define ADDR_MAX 100
//定义联系人数据 结构
//姓名 性别 年龄 电话 地址
typedef struct personInfo
{
	char name[NAME_MAX];
	char gender[GENDER_MAX];
	int age;
	char tel[TEL_MAX];
	char addr[ADDR_MAX];
}peoInfo;


//要用到顺序表相关的方法,对通讯录的操作实际就是对顺序表进行操作
//给顺序表改个名字,叫做通讯录


//这是前置声明
typedef struct SeqList Contact; //sl
//为结构体类型 struct SeqList 定义了一个新的类型别名 Contact。
//通讯录相关的方法

//通讯录的初始化
void ContactInit(Contact* con);
//通讯录的销毁
void ContactDesTroy(Contact* con);
//通讯录添加数据
void ContactAdd(Contact* con);
//通讯录删除数据
void ContactDel(Contact* con);
//通讯录的修改
void ContactModify(Contact* con);
//通讯录查找
void ContactFind(Contact* con);
//展示通讯录数据
void ContactShow(Contact* con);

2. Contact.c文件

c 复制代码
#include"Contact.h"
#include"SeqList.h"
#include <string.h>
#define _CRT_SECURE_NO_WARNINGS 1
//通讯录的初始化
void ContactInit(Contact* con)//sl
{
	//实际上要进行的是顺序表的初始化
	//顺序表的初始化已经实现好了
	SLInit(con);
}
//通讯录的销毁
void ContactDesTroy(Contact* con)
{
	SLDestroy(con);
}
//通讯录添加数据
void ContactAdd(Contact* con)
{
	//获取用户输入的内容:姓名+性别+年龄+电话+地址
	peoInfo info;
	printf("请输入要添加的联系人姓名:\n");
	scanf("%s", info.name);

	printf("请输入要添加的联系人性别:\n");
	scanf("%s", info.gender);

	printf("请输入要添加的联系人年龄:\n");
	scanf("%d", &info.age);

	printf("请输入要添加的联系人电话:\n");
	scanf("%s", info.tel);

	printf("请输入要添加的联系人住址:\n");
	scanf("%s", info.addr);

	//往通讯录中添加联系人数据
	SLPushBack(con, info);
}


//根据联系人姓名来查找联系人存不存在
int FindByName(Contact* con, char name[])
{
	for (int i = 0; i < con->size; i++)
	{
		if (0 == strcmp(con->arr[i].name, name))
		{
			//找到了
			return i;
		}
	}
	//没有找到
	return -1;
}

//通讯录删除数据
void ContactDel(Contact* con)
{
	//要删除的数据必须要存在,才能执行删除操作
	//查找
	char name[NAME_MAX];
	printf("请输入要删除的联系人姓名:\n");
	scanf("%s", name);

	int find = FindByName(con, name);
	if (find < 0)
	{
		printf("要删除的联系人数据不存在!\n");
		return;
	}
	//要删除的联系人数据存在--->知道了要删除的联系人数据对应的下标
	SLErase(con, find);
	printf("删除成功!\n");
}
//展示通讯录数据
void ContactShow(Contact* con)
{
	//表头:姓名  性别 年龄 电话  地址
	printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
	//遍历通讯录,按照格式打印每个联系人数据
	for (int i = 0; i < con->size; i++)
	{
		printf("%3s %3s %5d %5s %4s\n", //手动调整一下格式
			con->arr[i].name,
			con->arr[i].gender,
			con->arr[i].age,
			con->arr[i].tel,
			con->arr[i].addr
		);
	}
}
//通讯录的修改
void ContactModify(Contact* con)
{
	//要修改的联系人数据存在
	char name[NAME_MAX];
	printf("请输入要修改的用户姓名:\n");
	scanf("%s", name);

	int find = FindByName(con, name);
	if (find < 0)
	{
		printf("要修改的联系人数据不存在!\n");
		return;
	}
	//存在直接修改
	printf("请输入新的姓名:\n");
	scanf("%s", con->arr[find].name);

	printf("请输入新的性别:\n");
	scanf("%s", con->arr[find].gender);

	printf("请输入新的年龄:\n");
	scanf("%d", &con->arr[find].age);

	printf("请输入新的电话:\n");
	scanf("%s", con->arr[find].tel);

	printf("请输入新的住址:\n");
	scanf("%s", con->arr[find].addr);

	printf("修改成功!\n");
}
//通讯录查找
void ContactFind(Contact* con)
{
	//11

	char name[NAME_MAX];
	printf("请输入要查找的联系人姓名\n");
	scanf("%s", name);

	int find = FindByName(con, name);
	if (find < 0)
	{
		printf("要查找的联系人数据不存在!\n");
		return;
	}
	// 姓名 性别 年龄 电话  地址
	// 11   11   11   11   11
	printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
	printf("%3s %3s %5d %5s %5s\n", //手动调整一下格式
		con->arr[find].name,
		con->arr[find].gender,
		con->arr[find].age,
		con->arr[find].tel,
		con->arr[find].addr
	);
}

3. Seqlist.h文件

c 复制代码
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include"Contact.h"
//定义顺序表的结构

//#define N 100
//
////静态顺序表
//struct SeqList
//{
//	int arr[N];
//	int size;//有效数据个数
//};

//typedef int SLDataType;//方便后续类型的替换
typedef peoInfo SLDataType;
//动态顺序表
typedef struct SeqList
{
	SLDataType* arr;
	int size; //有效数据个数
	int capacity; //空间大小
}SL;

//typedef struct SeqList SL;

//顺序表初始化
void SLInit(SL* ps);
//顺序表的销毁
void SLDestroy(SL* ps);
void SLPrint(SL s);

//头部插入删除 / 尾部插入删除
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);

void SLPopBack(SL* ps);
void SLPopFront(SL* ps);

//指定位置之前插入/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
int SLFind(SL* ps, SLDataType x);

4. Seqlist.c文件

c 复制代码
#include"SeqList.h"
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}
//顺序表的销毁
void SLDestroy(SL* ps)
{
	if (ps->arr) //等价于  if(ps->arr != NULL)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}
void SLCheckCapacity(SL* ps)
{
	//插入数据之前先看空间够不够
	if (ps->capacity == ps->size)
	{
		//申请空间
		//malloc calloc realloc  int arr[100] --->增容realloc
		//三目表达式
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//要申请多大的空间
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);//直接退出程序,不再继续执行
		}
		//空间申请成功
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	////温柔的解决方式
	//if (ps == NULL)
	//{
	//	return;
	//}
	assert(ps); //等价与assert(ps != NULL)

	//ps->arr[ps->size] = x;
	//++ps->size;
	SLCheckCapacity(ps);
	ps->arr[ps->size++] = x;
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	//先让顺序表中已有的数据整体往后挪动一位
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//arr[1] = arr[0]
	}
	ps->arr[0] = x;
	ps->size++;
}

//void SLPrint(SL s)
//{
//	for (int i = 0; i < s.size; i++)
//	{
//		printf("%d ", s.arr[i]);
//	}
//	printf("\n");
//}
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//顺序表不为空
	//ps->arr[ps->size - 1] = -1;
	--ps->size;
}
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);

	//数据整体往前挪动一位
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1]; //arr[size-2] = arr[size-1]
	}
	ps->size--;
}
//在指定位置之前插入数据
// 1 2   size = 2
//pos 0 -1 100000
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	//插入数据:空间够不够
	SLCheckCapacity(ps);
	//让pos及之后的数据整体往后挪动一位
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//arr[pos+1] = arr[pos]
	}
	ps->arr[pos] = x;
	ps->size++;
}
//删除指定位置的数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);

	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}
//查找
//int SLFind(SL* ps, SLDataType x)
//{
//	assert(ps);
//	for (int i = 0; i < ps->size; i++)
//	{
//		if (ps->arr[i] == x)
//		{
//			//找到啦
//			return i;
//		}
//	}
//	//没有找到
//	return -1;
//}

5. test.c文件

c 复制代码
#include"SeqList.h"

//void SLTest01()
//{
//	SL sl;
//	SLInit(&sl);
//	//增删查改操作
//	//测试尾插
//	SLPushBack(&sl, 1);
//	SLPushBack(&sl, 2);
//	SLPushBack(&sl, 3);
//	SLPushBack(&sl, 4);
//	SLPrint(sl);//1 2 3 4
//
//	//SLPushFront(&sl, 5);
//	//SLPushFront(&sl, 6);
//
//	//测试尾删
//	SLPopBack(&sl);
//	SLPrint(sl);//1 2 3 
//	SLPopBack(&sl);
//	SLPrint(sl);
//	SLPopBack(&sl);
//	SLPrint(sl);
//	SLPopBack(&sl);
//	SLPrint(sl);
//	SLPopFront(&sl);
//	SLPrint(sl);
//	//...........
//	SLDestroy(&sl);
//}

//void SLTest02()
//{
//	SL sl;
//	SLInit(&sl);
//	SLPushBack(&sl, 1);
//	SLPushBack(&sl, 2);
//	SLPushBack(&sl, 3);
//	SLPushBack(&sl, 4);
//	SLPrint(sl);//1 2 3 4
//	//测试指定位置之前插入数据
//	//SLInsert(&sl, 1, 99);
//	//SLInsert(&sl, sl.size, 88);
//
//	//测试删除指定位置的数据
//	//SLErase(&sl, 1);
//	//SLPrint(sl);//1 3  4
//
//	//测试顺序表的查找
//	int find = SLFind(&sl, 40);
//	if (find < 0)
//	{
//		printf("没有找到!\n");
//	}
//	else {
//		printf("找到了!下标为%d\n",find);
//	}
//	SLDestroy(&sl);
//}

////通讯录的测试方法
//void ContactTest01()
//{
//	Contact con;//创建的通讯录对象  实际上就是 顺序表对象,等价于SL sl
//	ContactInit(&con);
//	ContactAdd(&con);
//	ContactAdd(&con);
//	ContactShow(&con);
//
//	//ContactDel(&con);
//	ContactModify(&con);
//	ContactShow(&con);
//	ContactFind(&con);
//
//	ContactDesTroy(&con);
//}
//int main()
//{
//	//SLTest01();
//	//SLTest02();
//	ContactTest01();
//	return 0;
//}


void menu()
{
	printf("******************通讯录******************\n");
	printf("*******1.增加联系人   2.删除联系人********\n");
	printf("*******3.修改联系人   4.查找联系人********\n");
	printf("*******5.展示联系人   0.   退出  *********\n");
	printf("******************************************\n");
}

int main()
{
	int op = -1;
	Contact con;
	ContactInit(&con);

	do {
		menu();
		printf("请选择您的操作:\n");
		scanf("%d", &op);

		//要根据对应的op执行不同的操作
		switch (op)
		{
		case 1:
			ContactAdd(&con);
			break;
		case 2:
			ContactDel(&con);
			break;
		case 3:
			ContactModify(&con);
			break;
		case 4:
			ContactFind(&con);
			break;
		case 5:
			ContactShow(&con);
			break;
		case 0:
			printf("退出通讯录....\n");
			break;
		default:
			printf("输入错误,请重新选择您的操作!\n");
			break;
		}
	} while (op != 0);

	ContactDesTroy(&con);
	return 0;
}

4. 顺序表经典算法题

4.1 移除元素

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

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

示例1:
输入: nums = [3,2,2,3] , val = 3
输出:2 , nums = [2,2]
函数应该返回新的长度2 并且 nums 中 的前两个元素均为2,你不需要考虑数组中超出新长度后面的元素,例如:函数返回的新长度是2,而 nums = [2,2,3,3 ]或nums = [2,2,0,0 ] 也会被视为正确答案。

思路解析:
思路1:建立一个新数组 tmp[ ] 然后遍历原数组 nums[ ] 只要值不等于 val 就把值拿下来插入到 tmp[ ] 中等于 val 就跳过继续往后走(题目要求不符合排除)
思路2:定义两个指针src 和 dst 都指向数组的第一个位置,当src指向位置的数据为 val 时 src 往后走一步,反之不是 val 就把 src 指向的值给到dst指向的位置,然后 dest 和 src 往后走一步

题解:

c 复制代码
int removeElement(int* nums, int numsSize, int val)
{
    //先创建两个变量分别指向数组的起始位置
    int src,dst;
    src = dst = 0;
    while(src< numsSize)//numsSize 表示数组长度
    {
        if(nums[src] == val)
        {
            src++;
        }
        else
        {
            //把src赋值给dst然后两指针一起++
            nums[dst] = nums[src];
            dst++;
            src++;
        }
    }
    //此时dst的值刚好就是新数组的有效长度
    return dst;
}

4.2 合并两个有序数组

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

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

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

示例1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。


思路解析:
思路1:
将nums2中的数据依次放入到nums1数组的后面,用排序算法对nums1进行排序
思路2:
定义一个指针 l3 指向nums1的最后一个位置,再定义两个指针 l1 和 l2 分别从各自数组的最后一个有效数据的位置开始从后往前遍历比大小,比谁大,谁大谁就放到 l3 指向的位置 ,然后 l3后退 大的那个指针也回退循环执行直到遍历完成。


题解:

c 复制代码
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
    int l1 = m-1;
    int l2 = n-1;
    int l3 = (m+n)-1;
    while(l1>=0 && l2>=0)
    {
        if(nums1[l1] < nums2[l2])
        {
            nums1[l3--] = nums2[l2--];
        }
        else
        {
            nums1[l3--] = nums1[l1--];
        }
    }
    //出了循环有两种情况:l1>=0或者l2>=0
    //是否还存在l1和l2同时小于0的情况?其实是不可能的
    //只需要处理一种情况:l2>=0(说明l2中的数据还没有完全放入nums1中)
    while(l2>=0)
    {
        nums1[l3--] = nums2[l2--];
    }
    //此时nums1中包含了nums2中的数据,nums1是升序数组
}

5. 顺序表的问题与思考

1. 顺序表中间/头部的插入删除涉及到移动数据导致时间复杂度为O(N)。

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

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

这些问题的本质只有一句话:顺序表要求数据存储在连续的内存空间中因为连续,所以:必须移动数据填补空隙插入删除慢;必须找连续的大空间扩容扩容消耗大;只能批量扩容空间浪费,这是物理结构决定的缺陷,无法优化。

那么接下来链表这种数据结构就应运而生了

相关推荐
Liangwei Lin1 小时前
LeetCode 45. 跳跃游戏 II
数据结构·算法·leetcode
啦啦啦_99991 小时前
4. 决策树剪枝
算法·决策树·剪枝
枕星而眠1 小时前
一篇吃透 C++ 核心基础:初始化、引用、指针、内联、重载、右值引用
开发语言·数据结构·c++·后端·visual studio
鹿角片ljp1 小时前
全局哈希去重原理与数据集实践
算法·安全·哈希算法
Paranoid-up1 小时前
安全启动和安全固件更新(SBSFU)3:加密基础
算法·安全·哈希算法·iap·安全启动·安全升级·sbsfu
Season4501 小时前
C/C++的类型转换
c语言·开发语言·c++
是wzoi的一名用户啊~1 小时前
Floyd 模版 弗洛伊德算法 模版
c++·算法·动态规划·图论·floyd
昵称小白1 小时前
图论专题(下)
算法·图论
懒惰的coder1 小时前
MPC算法
算法