顺序表的基本操作,唯一难点在于[[#增删]]时,位序(从1开始)与数组下标(从0开始)不一致。在处理此两者关系时,只需记住位序物理上是数组下标的后一位。比如 L->length 作为下标,指向数组最后一位的后一位。脑袋中有这一层映射,算法就不会写错。
ADT接口
#define MaxSize 50
typedef int ElemType;
/* 顺序表定义(408 王道标准) */
typedef struct {
ElemType data[MaxSize];
int length;
} SqList;
/* ── 简单操作(前缀 SL_ 避免与链表函数名冲突) ── */
void SL_InitList (SqList *L);
void SL_DestroyList (SqList *L);
bool SL_ListEmpty (SqList L);
int SL_ListLength (SqList L);
void SL_PrintList (SqList L);
/* 按位序插入/删除(位序从 1 开始) */
bool SL_ListInsert (SqList *L, int i, ElemType e);
bool SL_ListDelete (SqList *L, int i, ElemType *e);
/* 按位序取值,按值查找位序(未找到返回 0) */
bool SL_GetElem (SqList L, int i, ElemType *e);
int SL_LocateElem (SqList L, ElemType e);
增删
顺序表按位序操作(从 1 开始),但底层 data[] 是数组下标(从 0 开始)。所有易错点都源于这层映射:
data 下标 = 位序 - 1
插入
for(int j = L->length; j >= i; j--)
L->data[j] = L->data[j-1];
L->data[i-1] = e;
循环中 j 不是位序,而是数组下标——它表示"待写入的位置":
| j 的值 | 操作 | 含义 |
|---|---|---|
L->length |
data[length] = data[length-1] |
最后一个元素右移一格 |
| … | … | 逐个右移 |
i |
data[i] = data[i-1] |
把位序 i 处的元素也腾出 |
循环结束后,data[i-1](位序 i 对应的下标)已空出,直接写入 e。
易错:若写成 j > i 配合 data[i] = e,位序 1 处的元素不会被移动,插入位置偏移一格。
删除
*e = L->data[i-1]; // 先保存被删元素
while(i < L->length) { // i 直接当循环变量
L->data[i-1] = L->data[i];
i++;
}
L->length--;
取出 data[i-1] 后,i 的"位序"使命已经结束,直接复用它做前移循环的下标。每轮 data[i-1] = data[i] 就是把后一个元素覆盖到前一个位置,无需额外引入变量。
小结
插入是从后往前腾位置,删除是从前往后填空位;写对的关键就是搞清楚循环变量到底是下标还是位序。