流心
发布于 2024-06-06 / 0 阅读
0

线性表-顺序存储结构

  • 物理上的存储方式就是在内存初始化一个地址,通过占位的形式,把一定的内存空间给占了,然后把相同的数据类型的数据元素依次放在这个内存中

  • 线性表顺序存储的结构代码:

#define MAXSIZE 20

typedef int ElemType;

typedef struct

{

  ElemType data [MAXSIZE];
  int length;  //线性表当前长度

}SqList;
  • 顺序存储结构封装需要三个属性

    • 存储空间的起始位置,数组 data,它的存储位置就是线性表存储空间的存储位置

    • 线性表最大存储容量,数组的长度 MaxSize

    • 线性表的当前长度:length

  • 数组的长度与线性表的当前长度需要区分,数组的长度是存放线性表的存储空间长度,一般初始化后不变,而线性表的当前长度是线性表中的元素的个数,是会变化的

地址计算方法

  • 假设 ElemType 占用的是 C 个存储单元(字节),那么线性表中第 i + 1 个数据元素和第 i 个数据元素的存储位置的关系是(LOC 表示获得存储位置的函数):LOC(ai+1) = LOC(ai) + c ;C 是存储宽度

  • 对于第 i 个数据元素 ai 的存储位置可以由 a1 推算得出:LOC(ai) = LOC(a1) + (i-1) * c

  • 通过这个公式可以随时计算出线性表中任意位置的地址,不管它是第一个还是最后一个,都是相同的时间,它的存储时间性能就是 O(1) 通常称为随机存储结构

插入操作

  • 刚才我们也谈到,这里的时间复杂度为O(1)我们现在来考虑’如果要实现 ListInsert(*L,i,e),即在线性表L中的第j个位置插入新元素

  • 插入操作

    (1)如果插入位置不合理,抛出异常;

    (2)如果线性表长度大于等于数组长度,则抛出异常或动态增加容量;

    (3)从最后一个元素开始向前遍历到第个位置,分别将它们都向后移动一个位置;

    (4)将要插入元素填入位置处;

    (5)表长加1。

Status ListInsert(SqList* L, int i, ElementType* e) {
    if (L->length == 100) {
        return ERROR; // 表满,无法插入
    }

    if (i < 1 || i > L->length + 1) {
        return ERROR; // 参数i不合法
    }

    if (i <= L->length) {
        for (int k = L->length - 1; k >= i - 1; k--) {
            L->data[k + 1] = L->data[k]; // 元素后移一位
        }
        L->data[i - 1] = e; // 插入新元素
    }
    else {
        // 当i等于L->length +1时,直接在表尾插入
        L->data[L->length] = e;
    }

    L->length++;
    return OK;
}

删除操作

  • 如果元素要插入到最后一个位置,或者删除最后一个元素,此时时间复杂度为O(1),因为不需要移动元素

  • 最坏的情况呢,如果元素要插入到第一个位置或者删除第一个元素,此时时间复杂度是多少呢?要移动所有的元素向后或者向前,所以这个时间复杂度为O)。平均的情况,由于元素插入到第个位置,或删除第个元素,需要移动一个元素。根据概率原理,每个位置插入或删除元素的可能性是相同的,也就说位置靠前,移动元素多,位置靠后,移动元素少。最终平均移动次数和最中间的那个元素的移动次数相等,为 n-1 / 2 。时间复杂度的推导,可以得出,平均时间复杂度还是O(n)。线性表的顺序存储结构,在读数据时,不管是哪个位置,时间复杂度都是O(1);而插入或删除时,时间复杂度都是O(n)。这就说明,它比较适合元素个数不太变化,而更多是存取数据的应用

  • 删除操作

    (1)如果删除位置不合理,抛出异常;

    (2)取出删除元素;

    (3)从册除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;

    (4)表长减1。

Status delete(SqList* L, int i, ElementType* e) {
    // 如果顺序表为空,无法删除
    if (L->length == 0) {
        return ERROR;
    }
    // 检查索引是否有效
    if (i < 1 || i > L->length) {
        return ERROR;
    }
    // 获取要删除的元素
    *e = L->data[i - 1];
    // 如果要删除的是不是最后一个元素,移动元素
    for (int k = i; k < L->length; k++) {
        L->data[k - 1] = L->data[k];  // 向前移动元素
    }
    // 减少顺序表的长度
    L->length--;
    return OK;
}

查找操作

  • 对于线性表的顺序存储结构来说,如果我们要实现GetElem操作,即将线性表L中的第个位置元素值返回,其实是非常简单的。就程序而言,只要的数值在数组下标范围内,就是把数组第1下标的值返回即可。

  • 查找操作

    对于线性表的顺序存储结构来说,如果我们要实现GetElem操作,即将线性表L中的

    第个位置元素值返回,只要的数值在数组下标范围内,就是把数组第1下标的值返回即可

Status GetElem(SqList L, int i, ElementType* e) {
	// 检查顺序表是否为空,或者索引i是否无效(小于1或大于顺序表长度)
	if (L.length == 0 || i < 1 || i > L.length) {
		return ERROR;  // 如果无效,返回错误
	}
	// 获取顺序表中第i个元素并赋值给指针e
	*e = L.data[i - 1];  // 注意L.data是从0开始,所以用i-1
	return OK;  // 返回成功
}

线性表的优缺点

  • 优点:无须为表示表中元素之间的逻辑关系而增加额外的存储空间可以快速地存取表中任一位置的元素

  • 缺点:插入和删除操作需要移动大量元素当线性表长度变化较大时,难以确定存储空间的容量造成存储空间的“碎片

所有程序代码包括应用

#include <stdio.h>

typedef struct {
    int length;
    int data[100];
} SqList;

typedef int ElementType;
typedef enum {
    ERROR = -1,
    OK = 0
}Status;

/*
    插入操作
   (1)如果插入位置不合理,抛出异常;
   (2)如果线性表长度大于等于数组长度,则抛出异常或动态增加容量;
   (3)从最后一个元素开始向前遍历到第个位置,分别将它们都向后移动一个位置;
   (4)将要插入元素填入位置处;
   (5)表长加1。
*/ 
    Status ListInsert(SqList* L, int i, ElementType* e) {
        if (L->length == 100) {
            return ERROR; // 表满,无法插入
        }

        if (i < 1 || i > L->length + 1) {
            return ERROR; // 参数i不合法
        }

        if (i <= L->length) {
            for (int k = L->length - 1; k >= i - 1; k--) {
                L->data[k + 1] = L->data[k]; // 元素后移一位
            }
            L->data[i - 1] = e; // 插入新元素
        }
        else {
            // 当i等于L->length +1时,直接在表尾插入
            L->data[L->length] = e;
        }

        L->length++;
        return OK;
    }
/*
    查找操作
    对于线性表的顺序存储结构来说,如果我们要实现GetElem操作,即将线性表L中的
    第个位置元素值返回,只要的数值在数组下标范围内,就是把数组第1下标的值返回即可
*/ 
Status GetElem(SqList L, int i, ElementType* e) {
	// 检查顺序表是否为空,或者索引i是否无效(小于1或大于顺序表长度)
	if (L.length == 0 || i < 1 || i > L.length) {
		return ERROR;  // 如果无效,返回错误
	}
	// 获取顺序表中第i个元素并赋值给指针e
	*e = L.data[i - 1];  // 注意L.data是从0开始,所以用i-1
	return OK;  // 返回成功
}

/*
    删除操作
    (1)如果删除位置不合理,抛出异常;
    (2)取出删除元素;
    (3)从册除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
    (4)表长减1。
*/
Status delete(SqList* L, int i, ElementType* e) {
    // 如果顺序表为空,无法删除
    if (L->length == 0) {
        return ERROR;
    }
    // 检查索引是否有效
    if (i < 1 || i > L->length) {
        return ERROR;
    }
    // 获取要删除的元素
    *e = L->data[i - 1];
    // 如果要删除的是不是最后一个元素,移动元素
    for (int k = i; k < L->length; k++) {
        L->data[k - 1] = L->data[k];  // 向前移动元素
    }
    // 减少顺序表的长度
    L->length--;
    return OK;
}

int main() {
    // 定义一个顺序表 list,它的数据类型是 SqList
    SqList list;
    // 设置顺序表的长度为 0
    list.length = 0;
    int j = 10;
    for (int i = 0; i < j; i++) {
        int e = 20 + i;
        /*
            调用 ListInsert 函数将元素 e 插入到顺序表 list 的第 1 个位置。
            &list 表示传递 list 的地址,因此对 list 的修改会影响到实际的顺序表。
            返回的 Status 存储插入操作的结果(可能是 OK 或 ERROR)。
        */
        Status result = ListInsert(&list, 1, e);
        if (result == OK) {
            printf("插入后的长度%d\n", list.length);
        }
        else {
            printf("插入失败");
        }
    }

    // 定义一个 ElementType 类型的变量 e 用来存储从顺序表中获取的元素
    ElementType e;
    // 调用 GetElem 函数,从顺序表 list 的第 1 个位置获取元素,并将该元素存储到 e 中
    Status get = GetElem(list, 1, &e);
    for (int k = 0; k < list.length; k++) {
        //printf("总共有%d个元素\n", list.length);
        printf("%d元素\n", list.data[k]);
    }
    printf("总共有%d个元素\n", list.length);
   
    // 删除操作
    Status dele = delete(&list, 1, &e);
    if (dele == OK) {
        printf("删除的元素是%d\n", e);
        printf("删除后的列表长度%d\n", list.length);
        printf("删除后的列表顺序");
        for (int j = 0; j < list.length; j++) {
            printf("%d,",list.data[j]);
        }
    }
}