【数据结构】线性表之顺序表

顺序表


一、简介

顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构,逻辑结构与存储位置吻合。

顺序表分为静态顺序表动态顺序表两种。二者差异如下:

相同点:
内存空间连续, 数据顺序存储

不同点:
(1)容量大小
静态顺序表再创建顺序表的时候容量已经确定为MAX_SIZE,不可以更改,而动态顺序表的容量是由malloc函数动态开辟,当容量不够用时可以增加容量capacity

(2)二者所占内存空间的位置不同。
静态定义一个顺序表, 顺序表所占的内存空间开辟在内存的静态区, 即所谓的函数栈上, 随着函数调用的结束, 这块内存区域会被系统自动回收;而动态生成一个顺序表, 顺序表所占的内存空间开辟在内存的动态区, 即所谓的堆内存上, 这块区域不会随着函数调用的结束被系统自动回收, 而是需要程序员主动去释放它.

静态、动态顺序表优缺点:

静态顺序表:
操作简单, 不用malloc函数,不用手动释放其内存空间, 不存在内存泄漏

动态顺序表:
可以动态开辟内存空间, 操作灵活, 相比于静态开辟空间也可以少空间浪费

二、静态顺序表

1、结构定义

静态顺序表不可扩展空间,使用前需要定义最大空间大小MAX_SIZE。表结构定义需要记录两项数据:

  • 顺序表申请的存储容量;
  • 顺序表的长度,也就是表中存储数据元素的个数;
#define MAX_SIZE    100
#define ERROR       0

typedef int ElemType;
typedef struct SeqList {
    ElemType data[MAX_SIZE];
    int length;
}SeqList, *pSeqList;

2、初始化

void InitSeqList(SeqList *seq)
{
    assert(nullptr != seq);
    memset(seq->data, 0, sizeof(ElemType)*MAX_SIZE);
    seq->length = 0;
}

3、插入元素

3.1 头插

头插,总是将新元素插入到顺序表的首地址位置,需要将顺序表的length个元素往后移动一个位置,时间复杂度O(n)。

void InsertFront(SeqList *seq, ElemType e)
{
    assert(nullptr != seq);
    if (seq->length == MAX_SIZE)
        printf("SeqList is FULL!\n");

    for (int i = seq->length; i > 0; --j)
        seq->data[i] = seq->data[i-1];
    seq->data[0] = e;
    seq->length++;
}
3.2 尾插

尾插,总是从顺序表的末尾插入,没有元素的移动,时间复杂度O(1)

void InsertBack(SeqList *seq, ElemType e)
{
    assert(nullptr != seq);
    if (seq->length == MAX_SIZE)
    {
         printf("SeqList is FULL!\n");
         return;
    }
    seq->data[seq->length++] = e;
}
3.3 任意指定位置插入

实现思路:

1.通过遍历,找到数据元素要插入的位置

2.将要插入位置元素以及后续的元素整体向后移动一个位置

3.将元素放到腾出来的位置(即指定位置)

void Insert(SeqList seq, ElemType e, int pos)
{
    assert(nullptr != seq);
    if (seq->length == MAX_SIZE)
    {
        printf("SeqList is FULL!\n");
        return;
    }

    if (seq->length == 0)
    {
        printf("SeqList is Empty!\n");
        p = 1;
    }

    for (int i = seq->length; i >= pos; --i)
        seq->data[i] = seq->data[i-1];
    seq->data[p-1] = e;
    seq->length++;
}

4、删除元素

4.1 头删

总是从顺序表的首地址开始删除元素,将其后的length-1个元素都需要向前移动一位置, 时间复杂度为O(n)

void DeleteFront(SeqList *seq)
{
    assert(nullptr != seq);
    if (seq->length == 0)
    {
        printf("SeqList is Empty!\n");
        return;
    }

    for (int i = 0; i < seq->length; ++i)
    {
        seq->data[i] = seq->data[i+1];
    }
    seq->length--;
}
4.2 尾删

尾删,总是从顺序表的尾部开始删除元素,没有元素的移动,时间复杂度为O(1)

void DeleteBack(SeqList *seq)
{
    assert(nullptr != seq);
    if (seq->length == 0)
    {
        printf("SeqList is Empty!\n");
        return;
    }
    seq->length--;
}
4.3 任意指定位置删除

只需找到目标元素,并将其后续所有元素整体前移 1 个位置即可。(后续元素整体前移一个位置,会直接将目标元素删除,可间接实现删除元素的目的)

void Delete(SeqList *seq, int pos)
{
    assert(nullptr != seq);
    if (seq->length == 0)
    {
        printf("SeqList is Empty!\n");
        return;
    }

    if (pos > seq->length || p < 1)
    {
        printf("ERROR!\n")
    }

    for (int i = pos-1; i < seq->length; ++i)
        seq->data[i] = seq->data[i+1];
    seq->length--;
}

5、查找

简单粗暴的方法就是遍历表数据,逐一进行比较判断。实现如下:

int Find(SeqList *seq, ElemType value)
{
    assert(nullptr != seq);
    for (int i = 0; i < seq->length; ++i)
    {
        if (seq->data[i] == value)
            return i;
    }

    return -1;
}

除此之外,可以对表数据先进行排序,然后再进行二分查找等方式。

6、排序(升序)

// 排序
void BubbleSort(SeqList *seq)
{
	assert(nullptr != seq);
	for (int i = 1; i < seq->length; ++i)
	{
		for (j = 0; j < seq->length - i; ++j)
		{
			if (seq->data[j] > seq->data[j+1])
			{
				int tmp = seq->data[j];
				seq->data[j] = seq->data[j+1];
				seq->data[j+1] = tmp;
			}
		}
	}
}

上述代码是简单等冒泡排序方式,当中还有可优化方案以及其它一些排序方法。

7、逆序反转

void Reverse(SeqList *seq)
{
    assert(nullptr != seq);
    int start = 0;
    int end = seq->length-1;
    while (start != end)
    {
        int temp = seq->data[start];
        seq->data[start] = seq->data[end];
        seq->data[end] = temp;
        start++;
        end--;
    }
}

三、动态顺序表

相比与静态顺序表,动态顺序表支持扩容机制。即动态开辟一块新的空间(一般为原空间的两倍),将原空间的数据拷贝到新的空间,然后让array指针指向新的空间并且释放旧空间。

1、结构定义

#define LIST_INT_SIZE   100  //线性表存储空间的初始分配量
#define LIST_INCREMENT  100  //线性表存储空间的分配增量

typedef int ElemType;
typedef struct
{
    ElemType *data;         //存储空间基址
    int length;             //当前长度
    int capacity;           //当前分配的存储容量(等于静态顺序表MAX_SIZE)
}SqList;

2、初始化和销毁

//初始化
void Init(SeqList *seq)
{
	seq->capacity = LIST_INT_SIZE;
	seq->data = (ElemType*)malloc(sizeof(ElemType) * seq->capacity);
	assert(pSeq->parray);

	pSeq->length = 0;
}

//销毁
void Destroy(SeqList *seq)
{
	free(seq->data);

	pSeq->capacity = 0;
	pSeq->data = nullptr;
	pSeq->length = 0;
}

3、扩容判断

void IsNeedExpand(SeqList *seq)
{
	//扩容条件
	if (seq->length < seq->capacity)
		return;

	//扩容
	seq->capacity *= 2;

	//1.申请新空间
	ElemType *newData = (ElemType*)malloc(sizeof(ElemType) * seq->capacity);
	assert(newData);
	//2.数据搬移
	for (int i = 0; i < seq->size; i++)
    {
		newData[i] = seq->data[i];
	}
	// 3. 释放旧空间,关联新空间
	free(seq->data);
	seq->data = newData;
}

4、其它操作

插入、删除、查找等操作基本和静态顺序表差不多,额外需要注意等是所有的插入都需要扩容判断处理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章