数据结构:线性表————顺序表的实现、项目和OJ题目(手把手教你写代码)

🌈个人主页:小新_-

🎈个人座右铭:“成功者不是从不失败的人,而是从不放弃的人!”🎈

🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝

🏆所属专栏: 话说那些与C++的爱恨情仇   欢迎订阅,持续更新中~~~

                      

                  ✨让小新带着你快乐的学习吧~✨

目录

1、线性表的定义

2、顺序表分类

3.动态顺序表的实现

准备工作

1、文件的准备

2、顺序表的定义

3、扩容

4、打印当前顺序表

1、初始化和销毁

2、插入

2.1尾插

2.2头插

2.3任意位置之前插入

3、删除

3.1头删

3.2尾删

3.3任意位置删除

4、查找表中元素

完整代码

4、基于顺序表实现通讯录项目

4.1功能要求

4.2通讯录初始化

4.3添加联系人

4.4查看某联系人是否存在

4.5删除联系人

4.6打印通讯录

4.7修改联系人

4.7查找联系人

完整代码

5、经典顺序表OJ

1.经典算法OJ题1: 移除元素(点击进入题目)

2.经典算法OJ题2: 合并俩个有序数组(点击进入题目)

6. 顺序表的问题及思考


【知识框架】

1、线性表的定义


线性表(List):零个或多个数据元素的有限序列。

线性表的数据集合为{a1,a2,…,an},假设每个元素的类型均为DataType。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系。

线性表特点:

  • 元素个数有限
  • 元素具有先后顺序(逻辑上的先后顺序)
  • 表中每个元素都是数据元素,每个元素都是单个元素
  • 表中元素的数据类型相同(意味着每个元素占用存储空间大小一样)
  • 表中元素具有抽象性,仅讨论元素间的逻辑关系,而不考虑元素究竟表示什么内容

注意:线性表是一种逻辑结构,表示元素间一对一的相邻关系。顺序表、链表是指存储结构

2、顺序表分类

顺序表和数组的区别
  顺序表的底层结构是数组,对数组的封装,实现了常⽤的增删改查等接⼝
顺序表分类
静态顺序表:

动态顺序表:

3.动态顺序表的实现

准备工作

1、文件的准备

首先我们创建三个文件,一个是sqlist.c用于实现各种函数接口,一个为sqlist.h用于放置函数声明,另外一个test.c用于测试函数的正确性。他们三个文件最后链接实现我们的动态顺序表。

要实现的函数接口,就放在下面啦!

sqlist.h

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
//动态顺序表
typedef int SLDataType;
typedef struct Seqlist {
	SLDataType* arr;//数据
	int capacity;//顺序表空间大小
	int size;//有效数据个数
}SL;
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps); //保持接口一致性

//顺序表的头部/尾部插入
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);

2、顺序表的定义

首先在顺序表.h的文件里,把顺序表这个结构体先定义下来,定义完后,可以用typedef改名,然后,我们要想这个数组里必须是int型的吗,不一定,那我就用typedef提前把int重类型一下,这样我们就可以不仅仅停留在int,若想改成其他类型,直接在typedef后将int改成你想在顺序表放的类型就可以了。

3、扩容

在插入之前我们要想咱们现在初始化容量数为0,得扩容呀,那我们就扩容定义\n\n如何来扩容那,我们一般规定扩容到原空间的两倍,扩容用到的函数是realloc函数,但刚开始是0,我们可以用三目操作符来判断如果空间为0,我们要给它4个空间数,若不是0,直接capacity乘2,接着再使用新的容量数乘每个类型的字节数放到realloc里就是开辟了新的空间。至于为什么是2,有相当严格的证明,这里不再赘述。

代码如下:

void SLCheckCapacity(SL* ps)//判断是否扩容
{
	if (ps->capacity == ps->size)
	{//扩容
		int newcapacity = ps->capacity = 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, 2 * newcapacity*sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		//扩容成功
		ps->arr = tmp;
		ps->capacity = newcapacity;

	}
}

4、打印当前顺序表

逻辑比较简单,不再赘述

代码如下:

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

1、初始化和销毁

分析:

初始化十分简单,将数组置为空,因为此时没有数据。顺序表的空间大小和有效数据个数置为0。我们的初始化就完成了。

void SLInit(SL* ps)//初始化
{
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

分析:销毁基本和初始化差不多,俗话说一切回到原点,只不过为了健壮性要断言ps指针,然后将其空间归还给操作系统。

void SLDestroy(SL* ps)
{
	assert(ps);
	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

2、插入

2.1尾插

分析:

尾插比较简单,空间足够,直接在顺序表尾部插入即可。不够就得先扩容,再插入

代码如下:

void SLPushBack(SL* ps, SLDataType x)//尾插
{
	assert(ps);
	SLCheckCapacity(ps);
	
	ps->arr[ps->size] = x;
	ps->size++;
}

2.2头插

分析:

头插,我们要先将原来的数据整体往后挪动一位,这要通过一个for循环来实现,然后让头结点的值等于我们要赋予的值。如果空间不够,就得先扩容

代码如下:

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];
	}
	ps->arr[0] = x;
	ps->size++;
}

2.3任意位置之前插入

分析:

首先,要删除的顺序表不能为空。再通过for循环让pos位置以及pos之后的元素向后挪动一位,再讲pos位置赋值为要给定的值即可

代码如下:

void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);
	for (int i =  ps->size ;i>pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}

3、删除

3.1头删

分析:

首先,删除的顺序表肯定不为空,在通过for循环将元素整体往前挪动一位,有效数据个数size-1即可

代码如下:

void SLPopFront(SL* ps)
{
	assert(ps);
	for (int i = 0; i < ps->arr[ps->size-1]; i++)
	{
		ps->arr[i] = ps->arr[i +1];
	}
	ps->size--;
}

3.2尾删

分析:

尾删其实很简单,只需将尾部元素置为0,size--即可。当然,顺序表为空,不能执行删除操作,所以要判断一下。

代码如下:

void SLPopBack(SL* ps)//尾部删除
{
	assert(ps);
	assert(ps->size);
	ps->arr[ps->size] = 0;
	ps->size--;
}

3.3任意位置删除

分析:

逻辑比较简单,首先判断是否为空。然后让pos之后的数据往前挪动一位,这样数据会被覆盖,也就完成了删除的效果。

代码如下:

void SLErase(SL* ps, int pos)
{
	assert(ps);
	SLCheckCapacity(ps);
	for (int i = pos; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}


4、查找表中元素

分析:遍历顺序表,找到了就返回元素下标,没找到返回-1.

代码如下:

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;
}

完整代码

sqlist.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"sqlist.h"
#include<assert.h>
void SLInit(SL* ps)//初始化
{
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
void SLCheckCapacity(SL* ps)//判断是否扩容
{
	if (ps->capacity == ps->size)
	{//扩容
		int newcapacity = ps->capacity = 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, 2 * newcapacity*sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		//扩容成功
		ps->arr = tmp;
		ps->capacity = newcapacity;

	}
}
void SLPushBack(SL* ps, SLDataType x)//尾插
{
	assert(ps);
	SLCheckCapacity(ps);
	
	ps->arr[ps->size] = x;
	ps->size++;

}
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];
	}
	ps->arr[0] = x;
	ps->size++;
}
void SLPopBack(SL* ps)//尾部删除
{
	assert(ps);
	assert(ps->size);
	ps->arr[ps->size] = 0;
	ps->size--;
}
void SLPopFront(SL* ps)
{
	assert(ps);
	for (int i = 0; i < ps->arr[ps->size-1]; i++)
	{
		ps->arr[i] = ps->arr[i +1];
	}
	ps->size--;
}
void SLPrint(SL* ps) //打印
{
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}
//指定位置之前插入数据
//删除指定位置数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);
	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)
{
	assert(ps);
	SLCheckCapacity(ps);
	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;
}
void SLDestroy(SL* ps)
{
	assert(ps);
	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;

}

4、基于顺序表实现通讯录项目

通讯录就是给顺序表套一层壳,实际上还是顺序表。只不过里面的数据类型变成了一个结构体

手机通讯录其实就是一个顺序表,顺序表的每一个成员都是一个结构体,结构体中包含联系人的

名字、性别、年龄、电话、地址等信息,只不过我们之前建立的顺序表里存储的是整形数据,而通讯录里存储的是结构体类型而已。

所以我们还要创建一个结构体来存储联系人信息。

#define MAX_NAME 20
#define MAX_GENDER 10
#define MAX_PHONE 20
#define MAX_ADDR 30
typedef struct contacts 
{
    char name[MAX_NAME];
    char gender[MAX_GENDER];
    int age;
    char phone[MAX_PHONE];
    char addr[MAX_ADDR];
}contacts;


 既然通讯录就是顺序表,那我们可以给顺序表改类型一个名字

struct SList;
typedef struct SList CON;


4.1功能要求


1)⾄少能够存储100个⼈的通讯信息

2)能够保存⽤⼾信息:名字、性别、年龄、电话、地址等

3)增加联系⼈信息

4)删除指定联系⼈

5)查找制定联系⼈

6)修改指定联系⼈

7)显示联系⼈信息

4.2通讯录初始化


void ContactInit(CON* con)
{
    SlInit(con);
}
通讯录的初始化也就是顺序表的初始化,我们直接调用之前的顺序表初始化函数即可。

4.3添加联系人

void AddContact(CON* con)
{
    contacts f;
    printf("请输入要添加的联系人姓名:");
    scanf("%s", f.name);
    printf("请输入要添加的联系人性别:");
    scanf("%s", f.gender);
    printf("请输入要添加的联系人年龄:");
    scanf("%d", &f.age);
    printf("请输入要添加的联系人电话:");
    scanf("%s", f.phone);
    printf("请输入要添加的联系人住址:");
    scanf("%s", f.addr);
    AddTail(con, f);
}


我们先定义一个联系人结构体,再添加联系人信息,最后调用尾插函数,把联系人尾插到通讯录中。

4.4查看某联系人是否存在

int FundByContact(CON* con, char name[])
{
    assert(con);
    for (int i = 0; i < con->size; i++)
    {
        if (0 == strcmp(con->arr[i].name, name))
        {
            return i;
        }
    }
    return -1;
}


遍历数组,如果遍历到了要查找的联系人的名字,就返回数组成员下标,否则返回一个小于0的数。

4.5删除联系人

void DelContact(CON* con)
{
    assert(con);
    char name[MAX_NAME];
    printf("请输入要删除的联系人姓名:\n");
    scanf("%s", name);
    int fund = FundByContact(con, name);
    if (fund < 0)
    {
        printf("该联系人不存在");
    }
    SpeLocDel(con, fund);
}


调用FundByContact函数,如果要删除的联系人存在,那么返回数组下标,在调用SpeLocDel函数,删除改下表对应的联系人。

4.6打印通讯录

void PrintContact(CON* con)
{
    printf("姓名  性别  年龄  电话  住址\n");
    for (int i = 0; i < con->size; i++)
    {
        printf("%s %s %d %s %s\n",
            con->arr[i].name,
            con->arr[i].gender,
            con->arr[i].age,
            con->arr[i].phone,
            con->arr[i].addr);
    }
}


遍历数组,打印结构体成员信息。

4.7修改联系人
 

void ModifyContact(CON* con)
{
    char name[MAX_NAME];
    printf("请输入要修改的联系人姓名");
    scanf("%s", name);
    int fund = FundByContact(con, name);
    if (fund < 0)
    {
        printf("该联系人不存在");
    }
    printf("请输入新的联系人姓名:\n");
    scanf("%s", con->arr[fund].name);
 
    printf("请输入新的联系人性别:\n");
    scanf("%s", con->arr[fund].gender);
 
    printf("请输入新的联系人年龄:\n");
    scanf("%d", &con->arr[fund].age);
 
    printf("请输入新的联系人电话:\n");
    scanf("%s", con->arr[fund].phone);
 
    printf("请输入新的联系人住址:\n");
    scanf("%s", con->arr[fund].addr);
 
}

如果要修改的联系人存在,用fund接收返回的下标,再通过下标修改联系人信息。

4.7查找联系人

void FundContact(CON* con)
{
    assert(con);
    char name[MAX_NAME];
    printf("请输入要查找的联系人姓名:\n");
    scanf("%s", name);
    int fund = FundByContact(con, name);
    if (fund < 0)
    {
        printf("该联系人不存在");
    }
    printf("%s %s %d %s %s\n",
    con->arr[fund].name,
    con->arr[fund].gender,
    con->arr[fund].age,
    con->arr[fund].phone,
    con->arr[fund].addr);
}

如果要查找的联系人存在,打印该联系人的信息。

完整代码

contest.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"
#include"sqlist.h"
#include<string.h>
void ContactInit(Contact* pcon)//实际初始化的还是顺序表
{
	SLInit(pcon);
}
void ContactDesTroy(Contact* pcon)
{
	SLDestroy(pcon);
}
//增加、删除、修改、查找、查看通讯录
int FindByName(Contact* pcon,char name[])
{
	for (int i = 0; i < pcon->size; i++)
	{
		if (strcmp(pcon->arr->name, name) == 0)
		{
			return i;
		}
	}
	return -1;
}
void ContactAdd(Contact* pcon)
{
	Info info;
	printf("请输入联系人姓名:\n");
	scanf("%s", info.name);
	printf("请输入联系人年龄:\n");
	scanf("%d", &info.age);
	printf("请输入联系人性别:\n");
	scanf("%s", info.gender);
	printf("请输入联系人住址:\n");
	scanf("%s", info.addr);
	printf("请输入联系人电话:\n");
	scanf("%s", info.tel);
	SLPushBack(pcon, info);
}
void ContactDel(Contact* pcon)
{
	//删除之前一定要先查找
	//找到了,可以删除
	//找不到,不能执行删除
	printf("请输入要删除的联系人姓名:\n");
	char name[NAME_MAX];
	scanf("%s", name);

	int findIndex = FindByName(pcon, name);
	if (findIndex < 0) {
		printf("要删除的联系人不存在!\n");
		return;
	}
	//执行删除操作
	SLErase(pcon, findIndex);
	printf("联系人删除成功!\n");
}
void ContactModify(Contact* pcon)
{
	char name[NAME_MAX];
	printf("请输入要修改的联系人姓名:\n");
	scanf("%s", name);

	int findIndex = FindByName(pcon, name);
	if (findIndex < 0)
	{
		printf("要修改的联系人不存在!\n");
		return;
	}
	//找到了,执行修改操作
		//int arr[10]   int arr[findIndex] = 100
		printf("请输入姓名:\n");
	scanf("%s", pcon->arr[findIndex].name);
	printf("请输入年龄:\n");
	scanf("%d", &pcon->arr[findIndex].age);
	printf("请输入性别:\n");
	scanf("%s", pcon->arr[findIndex].gender);
	printf("请输入电话:\n");
	scanf("%s", pcon->arr[findIndex].tel);
	printf("请输入地址:\n");
	scanf("%s", pcon->arr[findIndex].addr);

	printf("联系人修改成功!\n");
}
void ContactFind(Contact* pcon)
{
	char name[NAME_MAX];
	printf("请输入要查找的用户姓名:\n");
	scanf("%s", name);

	int findIndex = FindByName(pcon, name);
	if (findIndex < 0) {
		printf("该联系人不存在!\n");
		return;
	}
	//找到了,打印一下查找的联系人信息
	printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "住址");
	printf("%s %s %d %s %s\n",
		pcon->arr[findIndex].name,
		pcon->arr[findIndex].gender,
		pcon->arr[findIndex].age,
		pcon->arr[findIndex].tel,
		pcon->arr[findIndex].addr
	);
}

void ContactShow(Contact* pcon)
{
	printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "住址");

	for (int i = 0; i < pcon->size; i++)
	{
		printf("%s %s %d %s %s\n",
			pcon->arr[i].name,
			pcon->arr[i].gender,
			pcon->arr[i].age,
			pcon->arr[i].tel,
			pcon->arr[i].addr
		);
	}
}

contest.h

#pragma once
#define NAME_MAX 100
#define GENDER_MAX 10
#define TEL_MAX 12
#define ADDR_MAX 100
#include<stdio.h>//暂时加上
//通讯录数据类型
typedef struct PersonInfo
{
	char name[NAME_MAX];
	int age;
	char gender[GENDER_MAX];
	char tel[TEL_MAX];
	char addr[ADDR_MAX];
}Info;
 struct Seqlist;
typedef struct Seqlist Contact;
//使用顺序表的前置声明
//struct SeqList;

//typedef struct SeqList Contact;

//通讯里提供的操作

//通讯录的初始化和销毁
void ContactInit(Contact* pcon);//实际初始化的还是顺序表
void ContactDesTroy(Contact* pcon);

//增加、删除、修改、查找、查看通讯录
void ContactAdd(Contact* pcon);
void ContactDel(Contact* pcon);
void ContactModify(Contact* pcon);
void ContactFind(Contact* pcon);
void ContactShow(Contact* pcon);

5、经典顺序表OJ

1.经典算法OJ题1: 移除元素(点击进入题目)

思路一:遍历数组,当数组中的等于val,就执行顺序表的删除操作
思路二:双指针法
我们定义俩个指针,起始地时候让他们指向arr[0]的地方,我们让j指针遍历数组,如果不等于val的值时就把j中的值赋给i,然后i++,j++;如果等于,我们就让j++。最后返回i的下标。此时i的下标就是数组的长度
代码如下:
int removeElement(int* nums, int numsSize, int val) {
    int i=0,j=0;
    while(j<numsSize)
    {
        if(nums[j]==val)
        {
         j++;
        }
        else{
             nums[i]=nums[j];
            i++;
            j++;
        }
    }
    return i;
}

2.经典算法OJ题2: 合并俩个有序数组(点击进入题目)

思路一:将num2合并到num1当中,在对合并的新数组排序即可
思路二:三指针法
定义三个指针,l1,l2,l3。l3指向nums1的头,l1指向num1的尾部,l2指向nums2的尾部,nums[l2]和nums2[l2]比大小,大的赋值给num1[l1],并将自身和l1加加。若跳出循环nums2还不为空怎么办?将nums2中的元素赋给nums[l3]就可以了。(这种方法只适用于已经排序好的)。
代码如下:
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]=nums1[l1];
                l3--;
                l1--;
            }
            else
            {
                nums1[l3]=nums2[l2];
                l3--;
                l2--;
            }
        }
        while(l2>=0)
        {
            nums1[l3]=nums2[l2];
            l3--;
            l2--;
        }

}

6. 顺序表的问题及思考

1. 中间/头部的插⼊删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷⻉数据,释放旧空间。会有不⼩的消耗。
3. 增容⼀般是呈2倍的增⻓,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到
200,我们再继续插⼊了5个数据,后⾯没有数据插⼊了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?那就是链表了,敬请期待~

最后,感谢大家的观看~~

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值