自学内容网 自学内容网

顺序表的基本操作

目录

命名规范

顺序表的定义

静态顺序表

动态顺序表

顺序表的基本操作

顺序表的初始化

顺序表的销毁

顺序表的打印

顺序表的容量检查

指定位置插入元素

指定位置删除元素

查找元素

在尾部添加一个元素

删除最后一个元素

在开头添加一个元素

删除首元素


命名规范

在数据结构中,我们会经常使用代码来表示链表,树,图等,那我们怎么对它们进行命名呢?

  • 首先我们不能用拼音来命名,这是极大的错误
  • 也不能用一些无意义的名称来表示

对于C++来说,我们一般将这些命名的和它的STL库类似即可

当我们讨论C++中数据结构的命名,并且想要遵循类似STL的命名约定时,我们可以避免使用具体的代码实现,而只是关注命名本身。

以下是一些仿照STL命名风格的常见数据结构命名示例:

  1. 顺序表
    • SequenceList
    • ArrayList
    • DynamicArray
  2. 链表
    • LinkedList
    • SinglyLinkedList(单链表)
    • DoublyLinkedList(双链表)
    • CircularLinkedList(循环链表)
    • Stack
    • DynamicStack
    • ArrayStack
  3. 队列
    • Queue
    • Deque(双端队列)
    • PriorityQueue(优先队列)
    • String(虽然C++标准库已经有一个std::string,但如果你需要自定义的字符串类,可以这样命名)
    • CustomString
    • TextBuffer
    • Tree
    • BinaryTree
    • AVLTree
    • BTree
    • Trie(字典树)
    • HuffmanTree(哈夫曼树)
    • Graph
    • DirectedGraph(有向图)
    • UndirectedGraph(无向图)
    • WeightedGraph(加权图)
    • AdjacencyMatrixGraph(邻接矩阵表示的图)
    • AdjacencyListGraph(邻接表表示的图)

请注意,上述命名只是为了提供一个思路,实际的命名应根据具体的应用场景和项目的约定来决定。

此外,如果你正在编写一个库或框架,并且想要提供类似STL的功能,那么确保你的命名风格与STL保持一致是很重要的,这样可以提高代码的可读性和可维护性。

最后,命名不仅仅是选择单词,还包括使用大小写、前缀和后缀等方式来区分不同的数据类型和特性。保持一致性是命名过程中的关键原则。

顺序表的定义

顺序表分为静态顺序表和动态顺序表

静态顺序表

typedef int SLDataType;
#define N 100000

// 静态顺序表 -- 开少了不够用 开多了浪费
struct SeqList
{
SLDataType a[N];
int size;
};

静态顺序表是一种在数据结构中常见的线性表实现方式,它使用定长数组来存储数据元素。这种数据结构在初始化时就确定了存储空间的大小,并在运行期间保持不变。

以下是对静态顺序表优缺点的分析:

优点

  1. 空间预先分配:静态顺序表在创建时就分配了固定的内存空间,这避免了在运行时频繁地申请或释放内存,从而减少了内存碎片的问题。
  2. 存取速度快:由于数据元素在内存中是连续存放的,因此可以通过下标直接计算出元素在内存中的位置,实现快速存取。
  3. 代码简单:静态顺序表的实现相对简单,因为数组的操作是基础的编程技能,所以理解和使用都比较容易。

缺点

  1. 空间限制:静态顺序表的大小在初始化时就已确定,因此它不能动态地扩展或缩小。如果预先分配的空间过大,会造成内存浪费;如果空间过小,又无法存储足够的数据,可能出现“溢出”问题。
  2. 插入和删除操作复杂:在静态顺序表中插入或删除元素时,可能需要移动大量的元素以保证数据的连续性,这导致插入和删除操作的时间复杂度较高,效率较低。
  3. 不灵活:由于静态顺序表的大小是固定的,因此在面对不确定数据规模的情况时,使用静态顺序表可能会带来问题。当需要存取的元素个数可能多于顺序表的元素个数时,就会出现问题。

综上所述,静态顺序表在数据规模确定且不需要频繁改变的情况下表现良好,但在面对动态变化的数据规模时,其缺点就显得尤为突出。因此,在选择使用静态顺序表时,需要根据具体的应用场景和需求进行权衡。

动态顺序表

typedef int SLDataType;
#define INIT_CAPACITY 4

// 动态顺序表 -- 按需申请
typedef struct SeqList
{
SLDataType* a;
int size;     // 有效数据个数
int capacity; // 空间容量
}SL;

动态顺序表是一种可以动态调整其大小的顺序表,它克服了静态顺序表在大小固定方面的限制。

以下是动态顺序表的优点和缺点:

优点

  1. 动态调整大小:动态顺序表能够根据数据的实际需求动态地扩展或缩小存储空间,从而避免了静态顺序表在存储空间分配上可能出现的浪费或不足的问题。
  2. 高效存储:由于动态顺序表在内存中连续存储数据,因此它保留了顺序表随机访问效率高的特点。通过下标可以直接访问任意位置的元素,这在处理大量数据时非常有用。
  3. 灵活性强:动态顺序表适用于数据规模不确定或可能频繁变化的情况。它可以随着数据的增减而自动调整大小,无需预先估计数据规模。

缺点

  1. 空间开销:虽然动态顺序表能够根据需要动态调整大小,但在扩展存储空间时可能需要额外的内存分配和数据复制操作。这可能导致一定的空间和时间开销。
  2. 插入和删除操作的效率:尽管动态顺序表能够动态调整大小,但在插入或删除元素时,仍可能需要移动其他元素以保持数据的连续性。这尤其在元素数量较大或需要频繁进行插入/删除操作时可能导致效率降低。
  3. 管理复杂:动态顺序表需要维护额外的元数据(如当前大小、最大容量等),并需要实现相应的内存管理操作(如分配、释放等)。这增加了实现的复杂性和出错的可能性。

总的来说,动态顺序表在处理动态变化的数据规模时具有较大的优势,但也需要考虑其带来的额外开销和实现的复杂性在选择使用动态顺序表时,需要根据具体的应用场景和需求进行权衡。

 接下来,我们将以动态顺序表为例,来讲述顺序表的增删查改等基本操作

顺序表的基本操作

我们将顺序表的所有操作都定义在一个叫SeqList的头文件里面,其中Seq是顺序的英文单词的缩写,List表示表的意思

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLDataType;
#define INIT_CAPACITY 4

// 动态顺序表 -- 按需申请
typedef struct SeqList
{
SLDataType* a;
int size;     // 有效数据个数
int capacity; // 空间容量
}SL;

// 增删查改
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);
void SLCheckCapacity(SL* ps);

void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
int SLFind(SL* ps, SLDataType x);

这些操作的前面都带有SL的前缀,是顺序表的缩写,后面则代表它们执行的操作

一,顺序表的初始化

void SLInit(SL* ps)
{
assert(ps);

ps->a = (SLDataType*)malloc(sizeof(SLDataType)* INIT_CAPACITY);
if (ps->a == NULL)
{
perror("malloc fail");
return;
}

ps->size = 0;
ps->capacity = INIT_CAPACITY;
}

这段代码是一个顺序表的初始化函数,函数名为 SLInit,它接受一个指向 SL 类型的指针 ps 作为参数。

下面是对这段代码的简要分析:

断言 (assert):

 assert(ps); 


断言用于检查传入的指针 ps 是否为 NULL。如果 ps 是 NULL,程序会在调试模式下终止执行,并显示一条错误消息。

这是一个很好的做法,因为它可以确保在尝试访问或修改 ps 指向的数据之前,ps 指向了一个有效的内存地址。

内存分配:

ps->a = (SLDataType*)malloc(sizeof(SLDataType)* INIT_CAPACITY); 

这行代码使用 malloc 函数为顺序表分配内存。malloc 分配了 INIT_CAPACITY 个 SLDataType 大小的内存块,并将其地址赋值给 ps->a。

这里假设 SLDataType 是顺序表中存储的数据类型,而 INIT_CAPACITY 是一个常量,表示顺序表初始的容量。

错误处理:

 if (ps->a == NULL)  
 {  
 perror("malloc fail");  
 return;  
 } 

如果 malloc 函数无法分配所需的内存(例如,由于内存不足),它会返回 NULL。

上面的代码检查 malloc 的返回值,并在返回 NULL 时调用 perror 函数来打印一个错误消息("malloc fail"),然后函数返回,不执行后续的代码。

这是一个基本的错误处理机制,用于处理内存分配失败的情况。

初始化顺序表的属性:

 ps->size = 0;  
 ps->capacity = INIT_CAPACITY; 

最后,代码初始化顺序表的两个属性:size 和 capacity。

size 表示顺序表中当前存储的元素数量,初始化为0,表示顺序表是空的。

capacity 表示顺序表的容量,即它可以存储的最大元素数量,初始化为 INIT_CAPACITY。

二,顺序表的销毁

void SLDestroy(SL* ps)
{
assert(ps);

free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}

这段代码是一个顺序表的销毁函数,函数名为 SLDestroy,它接受一个指向 SL 类型的指针 ps 作为参数。

下面是对这段代码的简要分析:

断言 (assert):

 assert(ps); 


断言用于检查传入的指针 ps 是否为 NULL。

这是为了确保在尝试访问或修改 ps 指向的数据之前,ps 指向了一个有效的内存地址。如果 ps 是 NULL,程序会在调试模式下终止执行,并显示一条错误消息。

释放内存:

 free(ps->a); 

这行代码使用 free 函数来释放之前通过 malloc 或其他内存分配函数为顺序表分配的内存。ps->a 指向了这块内存的起始地址,调用 free 之后,这块内存就被标记为可重新分配了。

这是一个非常重要的步骤,因为如果不释放已分配的内存,会导致内存泄漏。

重置指针和属性:

 ps->a = NULL;  
 ps->capacity = ps->size = 0; 

释放内存之后,将 ps->a 设置为 NULL 是一个好习惯,这可以避免在之后的代码中意外地使用已释放的内存。此外,将 ps->capacity 和 ps->size 重置为0,确保顺序表的状态正确地反映了它已经被销毁。

三,顺序表的打印

void SLPrint(SL* ps)
{
assert(ps);

for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}

里面用了一个 for 循环,用于遍历顺序表中的每一个元素。

循环变量 i 从0开始,一直到 ps->size - 1(即顺序表中最后一个元素的索引)。

ps->size 表示顺序表中当前存储的元素数量

四,顺序表的容量检查

void SLCheckCapacity(SL* ps)
{
assert(ps);

if (ps->size == ps->capacity)
{
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity*2);
if (tmp == NULL)
{
perror("realloc fail");
return;
}

ps->a = tmp;
ps->capacity *= 2;
}
}

SLCheckCapacity 函数用于检查顺序表的当前容量是否足够。如果顺序表已满(即 ps->size 等于 ps->capacity),则该函数会尝试扩大顺序表的容量。

下面是对这个函数的详细分析:

断言 (assert):

 assert(ps); 

断言用于检查传入的指针 ps 是否为 NULL。这是为了确保在尝试访问或修改 ps 指向的顺序表之前,ps 指向了一个有效的内存地址。

检查容量:

 if (ps->size == ps->capacity) 


这个条件语句检查顺序表是否已满。

如果顺序表的当前大小 ps->size 等于其容量 ps->capacity,则说明顺序表已经没有额外的空间来存储新的元素了。

扩大容量:

如果顺序表已满,接下来的代码块将尝试扩大顺序表的容量。

 SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity*2); 


使用 realloc 函数来重新分配内存。

这里,我们将顺序表的容量扩大两倍(ps->capacity*2)。realloc 尝试在内存中找到足够大的连续空间来容纳扩大后的顺序表,如果成功,它返回指向新内存块的指针;如果失败,它返回 NULL。

错误处理:
 

 if (tmp == NULL)  
 {  
 perror("realloc fail");  
 return;  
 } 

如果 realloc 函数返回 NULL,说明内存分配失败。

这时,我们使用 perror 函数打印一个错误消息("realloc fail"),然后函数返回,不执行后续的代码。

更新指针和容量:

 ps->a = tmp;  
 ps->capacity *= 2; 


如果 realloc 成功,我们将 ps->a 更新为指向新的内存块,并将 ps->capacity 更新为新的容量(原来的两倍)。这样,顺序表就有了更多的空间来存储新的元素。

五,指定位置插入元素

void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);

SLCheckCapacity(ps);

int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
--end;
}

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

这个 SLInsert 函数的作用是在顺序表的指定位置 pos 插入一个元素 x。

下面是对这个函数的详细分析:

断言 (assert):

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

第一个断言检查传入的 ps 指针是否有效,确保它不为 NULL。

第二个断言检查插入位置 pos 是否在合法范围内,即 pos 必须大于等于0(顺序表的起始位置)且小于等于 ps->size(顺序表的当前大小)。

检查容量:

 SLCheckCapacity(ps); 

在插入元素之前,调用 SLCheckCapacity 函数检查顺序表的当前容量是否足够。如果不够,则尝试扩大容量。这是为了避免在插入过程中因为空间不足而引发错误。

移动元素:

 int end = ps->size - 1;  
 while (end >= pos)  
 {  
 ps->a[end + 1] = ps->a[end];  
 --end;  
 } 

这一部分代码负责将位置 pos 及之后的元素向后移动一位,以便为新元素腾出空间。

变量 end 初始化为顺序表的最后一个元素的索引。

while 循环从 end 开始,直到 pos(不包括 pos),将每个元素的值复制到其后一个位置。这样,从 pos 开始的位置就空出来了,可以插入新元素。

插入新元素:

 ps->a[pos] = x; 

在移动完元素后,将新元素 x 插入到位置 pos。

更新顺序表大小:

 ps->size++; 

最后,将顺序表的大小 ps->size 增加1,以反映新元素的插入。

需要注意的是,由于顺序表在内存中是连续存储的,插入操作的时间复杂度是 O(n),其中 n 是顺序表的大小。如果频繁进行插入操作,可能会影响程序的性能。 

六,指定位置删除元素

void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);

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

ps->size--;

这个 SLErase 函数的作用是从顺序表中删除指定位置 pos 的元素。

下面是对这个函数的详细分析:

断言 (assert):

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

第一个断言确保传入的 ps 指针不为 NULL。第二个断言检查删除位置 pos 是否在合法范围内,即 pos 必须大于等于0(顺序表的起始位置)且小于 ps->size(顺序表的当前大小)。

移动元素:

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

这一部分代码负责将位置 pos + 1 及之后的元素向前移动一位,以覆盖掉位置 pos 的元素。

变量 begin 初始化为要删除元素的后一个元素的索引。 

while 循环从 begin 开始,直到顺序表的最后一个元素,将每个元素的值复制到其前一个位置。

这样,位置 pos 的元素就被覆盖了,从顺序表中“删除”了。

更新顺序表大小:

 ps->size--; 

最后,将顺序表的大小 ps->size 减少1,以反映元素的删除。

需要注意的是,SLErase 函数并没有真正地释放被删除元素所占用的内存空间。它只是通过移动后续元素来覆盖掉被删除的元素,并将顺序表的大小减1。

因此,顺序表所占用的总内存大小并没有改变。如果需要频繁地删除顺序表中的元素,并且希望有效地管理内存使用,可能需要考虑使用其他数据结构(如链表)或实现更复杂的内存管理策略。

此外,SLErase 函数的时间复杂度也是 O(n),其中 n 是顺序表的大小,因为需要移动 pos 位置之后的所有元素。如果顺序表很大且需要频繁删除元素,这可能会成为性能瓶颈。

七,查找元素

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

return -1;
}

这个 SLFind 函数的作用是在顺序表中查找指定元素 x 的位置。

下面是对这个函数的详细分析:

断言 (assert):

 assert(ps); 

断言用于检查传入的 ps 指针是否有效,确保它不为 NULL。

遍历顺序表:

 for(int i = 0; i < ps->size; ++i) 

使用一个 for 循环遍历顺序表中的每一个元素。

循环变量 i 从0开始,直到 ps->size - 1(顺序表中最后一个元素的索引)。

查找元素:

 if (ps->a[i] == x)  
 {  
 return i;  
 } 

在循环体内部,检查当前元素 ps->a[i] 是否等于要查找的元素 x。

如果相等,说明找到了元素 x,函数立即返回该元素在顺序表中的位置 i。

未找到元素:

 return -1; 

如果循环结束后都没有找到元素 x,函数返回 -1,表示在顺序表中未找到该元素。

综上所述,SLFind 函数通过遍历顺序表来查找指定元素,并返回该元素在顺序表中的位置。如果未找到元素,则返回 -1。

这个函数的时间复杂度是 O(n),其中 n 是顺序表的大小,因为需要遍历整个顺序表来查找元素。如果顺序表很大且查找操作频繁,可能需要考虑使用其他数据结构(如哈希表)来优化查找性能。

八,在尾部添加一个元素

void SLPushBack(SL* ps, SLDataType x)
{
//assert(ps);

 扩容
//SLCheckCapacity(ps);

ps->a[ps->size] = x;
ps->size++;
//ps->a[ps->size++] = x;

SLInsert(ps, ps->size, x);
}

SLPushBack 函数用于在顺序表的末尾添加一个元素 x。在这个函数中,直接调用了之前定义的 SLInsert 函数来完成添加操作。

下面是对这个函数的详细分析:

调用 SLInsert 函数:

 SLInsert(ps, ps->size, x); 

这里,ps 是指向顺序表的指针,ps->size 是顺序表当前的元素数量(即末尾的下一个位置),x 是要添加的元素。

通过调用 SLInsert 函数,并传入这些参数,可以在顺序表的末尾插入元素 x。

SLInsert 函数会首先检查顺序表的容量是否足够(通过调用 SLCheckCapacity 函数),如果不足则进行扩容。

然后,它会将位置 ps->size 及之后的元素向后移动一位,为新元素腾出空间。最后,将新元素 x 插入到位置 ps->size,并更新顺序表的大小 ps->size。

注释掉的代码:

 assert(ps);  
   // 扩容  
 SLCheckCapacity(ps);  
   ps->a[ps->size] = x;  
 ps->size++;  
 ps->a[ps->size++] = x; 

这部分代码是 SLPushBack 函数的一个简单实现,但它被注释掉了。

在这个简单实现中,首先检查 ps 是否为 NULL(尽管这行代码被注释掉了,但在实际使用中通常是必要的)。

然后,它尝试通过 SLCheckCapacity 函数来扩容顺序表(如果需要的话)。

最后,它将新元素 x 添加到顺序表的末尾,并更新顺序表的大小。

不过,由于这部分代码已经被注释掉了,所以实际上不会被执行。取而代之的是调用 SLInsert 函数来完成添加操作,这种做法更加灵活,因为 SLInsert 函数可以用于在顺序表的任意位置插入元素,而不仅仅是末尾。

总的来说,SLPushBack 函数通过调用 SLInsert 函数实现了在顺序表末尾添加元素的功能。这种做法利用了现有函数的功能,减少了重复代码,提高了代码的可维护性和可读性。

九,删除最后一个元素

void SLPopBack(SL* ps)
{
//assert(ps);

 暴力检查
//assert(ps->size > 0);

 温柔的检查
if (ps->size == 0)
////return;

ps->a[ps->size - 1] = 0;
//ps->size--;

SLErase(ps, ps->size-1);
}

SLPopBack 函数用于删除顺序表的最后一个元素。在这个函数中,直接调用了之前定义的 SLErase 函数来完成删除操作。

下面是对这个函数的详细分析:

调用 SLErase 函数:

 SLErase(ps, ps->size-1); 

这里,ps 是指向顺序表的指针,ps->size-1 是顺序表最后一个元素的索引。通过调用 SLErase 函数,并传入这些参数,可以删除顺序表的最后一个元素。

SLErase 函数会首先将位置 pos + 1 及之后的元素向前移动一位,以覆盖掉位置 pos 的元素。

在这个例子中,pos 是 ps->size-1,即最后一个元素的索引。

因此,所有元素都会向前移动一位,最后一个元素的位置将变为无效,从而实现了删除操作。最后,SLErase 函数会更新顺序表的大小 ps->size,将其减1。

注释掉的代码:


 assert(ps);  
   // 暴力检查  
 assert(ps->size > 0);  
  

 // 温柔的检查  
 if (ps->size == 0)  
 return;  
   ps->a[ps->size - 1] = 0;  
 ps->size--; 

在这个简单实现中,首先检查 ps 是否为 NULL(尽管这行代码被注释掉了,但在实际使用中通常是必要的)。然后,它使用断言来检查顺序表的大小是否大于0,以确保不会尝试删除一个空顺序表的元素(这是所谓的“暴力检查”)。

接下来,它使用了一个条件语句来进行一个更“温柔”的检查,如果顺序表的大小为0,则直接返回,不执行任何操作。然后,它将顺序表的最后一个元素设置为0(尽管这通常不是删除元素的标准做法,因为只是将元素值设置为0,并没有真正减少顺序表的大小)。最后,它更新顺序表的大小 ps->size,将其减1。

不过,由于这部分代码已经被注释掉了,所以实际上不会被执行。取而代之的是调用 SLErase 函数来完成删除操作,这种做法更加规范且有效。它确保了元素的真正删除,并正确更新了顺序表的大小。

十,在开头添加一个元素

void SLPushFront(SL* ps, SLDataType x)
{
/*assert(ps);

SLCheckCapacity(ps);

int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
--end;
}

ps->a[0] = x;
ps->size++;*/

SLInsert(ps, 0, x);
}

SLPushFront 函数用于在顺序表的开头添加一个元素 x。与 SLPushBack 函数类似,这个函数通过调用之前定义的 SLInsert 函数来完成添加操作。

下面是对这个函数的详细分析:

调用 SLInsert 函数:

 SLInsert(ps, 0, x); 

这里,ps 是指向顺序表的指针,0 是要插入元素的位置(即顺序表的开头),x 是要添加的元素。通过调用 SLInsert 函数,并传入这些参数,可以在顺序表的开头插入元素 x。

SLInsert 函数会首先检查顺序表的容量是否足够(通过调用 SLCheckCapacity 函数),如果不足则进行扩容。

然后,它会将位置 0 及之后的元素向后移动一位,为新元素腾出空间。

最后,将新元素 x 插入到位置 0,并更新顺序表的大小 ps->size。

注释掉的代码:

 assert(ps);  
   SLCheckCapacity(ps);  
   int end = ps->size - 1;  
 while (end >= 0)  
 {  
  ps->a[end + 1] = ps->a[end];  
  --end;  
 }  
  ps->a[0] = x;  
 ps->size++; 

这部分代码是 SLPushFront 函数的一个简单实现,但它被注释掉了。

在这个简单实现中,首先检查 ps 是否为 NULL(尽管这行代码被注释掉了,但在实际使用中通常是必要的)。

然后,它尝试通过 SLCheckCapacity 函数来扩容顺序表(如果需要的话)。

接下来,使用一个 while 循环将顺序表中原有的所有元素向后移动一位,以便在开头为新元素腾出空间。

最后,将新元素 x 插入到位置 0,并更新顺序表的大小。

不过,由于这部分代码已经被注释掉了,所以实际上不会被执行。取而代之的是调用 SLInsert 函数来完成添加操作,这种做法更加灵活,因为 SLInsert 函数可以用于在顺序表的任意位置插入元素,而不仅仅是开头。

十,删除首元素

void SLPopFront(SL* ps)
{
//assert(ps);
//assert(ps->size > 0);

//int begin = 1;
//while (begin < ps->size)
//{
//ps->a[begin - 1] = ps->a[begin];
//++begin;
//}

//ps->size--;

SLErase(ps, 0);
}

SLPopFront 函数用于删除顺序表的第一个元素。在这个函数中,直接调用了之前定义的 SLErase 函数来完成删除操作。

下面是对这个函数的详细分析:

调用 SLErase 函数:
 

SLErase(ps, 0); 

这里,ps 是指向顺序表的指针,0 是要删除元素的位置(即顺序表的第一个元素)。通过调用 SLErase 函数,并传入这些参数,可以删除顺序表的第一个元素。

SLErase 函数会首先将位置 pos + 1 及之后的元素向前移动一位,以覆盖掉位置 pos 的元素。

在这个例子中,pos 是 0,即第一个元素的索引。

因此,所有元素都会向前移动一位,第一个元素的位置将变为无效,从而实现了删除操作。

最后,SLErase 函数会更新顺序表的大小 ps->size,将其减1。

注释掉的代码:

 assert(ps);  
 assert(ps->size > 0);  
   int begin = 1;  
 while (begin < ps->size)  
 {  
 ps->a[begin - 1] = ps->a[begin];  
 ++begin;  
 }  
   ps->size--; 

这部分代码是 SLPopFront 函数的一个简单实现,但它被注释掉了。

在这个简单实现中,首先检查 ps 是否为 NULL 和顺序表的大小是否大于0(尽管这些断言被注释掉了,但在实际使用中通常是必要的)。

然后,它使用一个 while 循环将顺序表中从第二个元素开始的所有元素向前移动一位,以便删除第一个元素。最后,更新顺序表的大小 ps->size,将其减1。

不过,由于这部分代码已经被注释掉了,所以实际上不会被执行。取而代之的是调用 SLErase 函数来完成删除操作,这种做法更加规范且有效。它确保了元素的真正删除,并正确更新了顺序表的大小。
 

使用

void TestSeqList1()
{
SL s;
SLInit(&s);
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
SLPushBack(&s, 5);
SLPushBack(&s, 6);
SLPushBack(&s, 7);
SLPushBack(&s, 8);
SLPushBack(&s, 9);
SLPrint(&s);

SLPopBack(&s);
SLPopBack(&s);
SLPrint(&s);

SLPopBack(&s);
SLPopBack(&s);
SLPopBack(&s);
SLPopBack(&s);
SLPopBack(&s);
SLPopBack(&s);
SLPopBack(&s);
//SLPopBack(&s);
SLPrint(&s);

SLPushBack(&s, 10);
SLPushBack(&s, 20);
SLPrint(&s);

SLDestroy(&s);
}

int main()
{
TestSeqList1();

return 0;
}


原文地址:https://blog.csdn.net/2301_80224556/article/details/137711297

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!