顺序表的基本操作

目录

命名规范

顺序表的定义

静态顺序表

动态顺序表

顺序表的基本操作

顺序表的初始化

顺序表的销毁

顺序表的打印

顺序表的容量检查

指定位置插入元素

指定位置删除元素

查找元素

在尾部添加一个元素

删除最后一个元素

在开头添加一个元素

删除首元素


命名规范

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

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

对于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+1是小于capacity-1的--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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/616151.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

4、jvm-垃圾收集算法与垃圾收集器

垃圾收集算法 分代收集理论 当前虚拟机的垃圾收集都采用分代收集算法&#xff0c;这种算法没有什么新的思想&#xff0c;只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代&#xff0c;这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 比如…

AI在运维实践中的价值提升

在2024年的AI赛道上&#xff0c;利用大数据 、机器学习算法、人工智能来改善运维效率已成为软件运营商发展的新主张&#xff0c;通过AI在运维流程的洞察、决策和执行&#xff0c;从而提升效率、减少故障时间&#xff0c;优化用户体验。通过分析大量数据来识别趋势和模式&#x…

海山数据库(He3DB)原理剖析:浅析Doris跨源分析能力

Doris湖仓分析背景&#xff1a; Doris多数据源功能演进 Doris的生态近年来围绕湖仓分析做了较多工作&#xff0c;Doris一直在积极拓宽大数据生态的OLAP分析市场&#xff0c;Doris2.0之后为了满足湖仓分析场景&#xff0c;围绕multi-catalog、数据缓存、容错、pipeline资源管理…

Facebook广告投放数据API对接流程

说明&#xff1a;仅供学习使用&#xff0c;请勿用于非法用途&#xff0c;若有侵权&#xff0c;请联系博主删除 作者&#xff1a;zhu6201976 一、需求背景 App在Facebook、Google等巨头进行广告投放&#xff0c;想要拿到实时广告投放效果数据&#xff0c;如曝光、点击、花费、触…

VM虚拟机Linux系统Redhat7.4版本进行网络配置

日常中自己搭建的虚拟机一般用到两种网络方式&#xff0c;第一种是仅主机模式、还有一种是NAT模式。 1、仅主机模式&#xff1a;可以和自己本地电脑&#xff0c;或者虚拟机和虚拟机之间进行网络通信&#xff0c;相当于一个局域网&#xff0c;是不能连接外网的。 2、NAT模式&a…

26、链表-环形链表II

思路&#xff1a; 这道题就是判断链表中是否有环&#xff0c;首先使用集合肯定可以快速地解决&#xff0c;比如通过一个set集合遍历&#xff0c;如果遍历过程中有节点在set中已经存在那么说明存在环。返回这个节点即可 第二种方式就是通过快慢指针方式寻找环。如何做呢&#xf…

Unity 获取RenderTexture像素颜色值

拿来吧你~ &#x1f9aa;功能介绍&#x1f32d;Demo &#x1f9aa;功能介绍 &#x1f4a1;不通过Texture2D 而是通过ComputerShader 提取到RenderTexture的像素值&#xff0c;效率有提升哦&#xff01; &#x1f4a1;通过扩展方法调用&#xff0c;方便快捷&#xff1a;xxxRT.G…

DRF多表关联的序列化和反序列化

DRF多表关联的序列化和反序列化 目录 DRF多表关联的序列化和反序列化序列化定制字段source一对多的序列化 多表关联的序列化方式1&#xff1a;在表模型中定义方法方式2&#xff1a;定制返回格式SerializerMethodField方式3&#xff1a;子序列化 多表关联的反序列化反序列化保存…

安达发|电子行业智能生产排程计划的实施

随着科技的不断发展&#xff0c;电子行业正面临着巨大的变革。在这个过程中&#xff0c;智能生产排程计划的实施成为了提高生产效率、降低成本的关键因素。本文将详细介绍电子行业智能生产排程计划的实施方法、优势以及可能遇到的挑战。 一、实施方法 1. 数据采集与分析&#x…

0101tomcat部署war访问mysql失败-容器间通信-docker项目部署

文章目录 一、简介二、部署1、mysql数据迁移2、docker部署redis3、docker部署tomcat并运行war包 三、报错四、解决1 分析2 解决 结语 一、简介 最近参与开发一个项目&#xff0c;其中一部分系统需要迁移。从阿里云迁移到实体服务器&#xff0c;使用docker部署。系统使用Java语…

【STK】手把手教你利用STK进行导弹和反导仿真03 - STK/MMT模块02 导弹飞行工具用户界面

MFT导弹飞行工具是MMT模块中的核心部分&#xff0c;它提供多级导弹飞行轨迹&#xff0c;易于在STK中分析和可视化。对于一个特定的导弹模型&#xff0c;可通过定义发射点和打击点及轨迹类型&#xff0c;生成飞行轨迹和飞行数据。 MFT提供代表多种导弹类型和性能参数的导弹数据库…

Windows server SMB服务 文件夹访问缓慢 解决方法

Windows server用了很久&#xff0c;一直有个问题没有解决&#xff0c;就是用手机访问SMB时&#xff0c;文件夹列出速度非常慢&#xff0c;今天去翻阅了一下官方文档&#xff0c;找到了解决办法。 更改注册表SMB服务的工作进程数 HKLM\System\CurrentControlSet\Control\Sessi…