数据结构-线性表(一)概念及基本操作

数据结构-线性表(二)单链表
数据结构-线性表(三)双链表
数据结构-线性表(四)循环链表

本文介绍了线性表的定义及基本操作以及顺序表示的实现代码!
Let’s go‍♂️

数据结构

线性表(一)基本概念及基本操作

思维导图

数据结构-线性表(一)概念及基本操作_第1张图片
数据结构-线性表(一)概念及基本操作_第2张图片

数据结构-线性表(一)概念及基本操作_第3张图片

数据结构-线性表(一)概念及基本操作_第4张图片

1 线性表的定义及基本操作

1.1 定义

线性表是具有相同数据类型的n(n≥0)个数据元素的有限序列,其中n为表长,当n = 0时线性表是一个空表。若用L命名线性表,则其一般表示为

L = ( a 1 , a 2 , … , a i , a i + 1 , … , a n ) L = (a_1, a_2, … , a_i, a_i+1, … , a_n) L=(a1,a2,,ai,ai+1,,an)

几个概念:
a i a_i ai 是线性表中的“第i个”元素线性表中的位序
a 1 a_1 a1 是表头元素; a n a_n an 是表尾元素。
除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继 。

1.2 基本操作

InitList(&L):初始化表。构造一个空的线性表L,分配内存空间

DestroyList(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间

ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e。

ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。

LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。

GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。

其他常用操作:

Length(L) 求表长。返回线性表L的长度,即L中数据元素的个数。

PrintList(L) 输出操作。按前后顺序输出线性表L的所有元素值。

Empty(L) 判空操作。若L为空表,则返回true,否则返回false。

2 线性表的顺序表示

2.1 定义

定义: 顺序表–用顺序存储的方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。

1、静态分配

给各个数据元素分配连续的存储空间,大小为MaxSize * sizeof(ElemType)

#include 
#define MaxSize 10 //定义最大长度
typedef struct SqList
{
    /* data */
    int data[MaxSize]; //用静态的“数组”存放数据元素
    int length;        //顺序表的当前长度
};

void InitList(SqList &L)
{
    L.length = 0; //顺序表初始长度为0;
}

int main()
{
    SqList L; //声明一个顺序表
    InitList(L); //初始化
    for (int i = 0; i < L.length; i++)
    {
        /* code */
        printf("data[%d]=%d\n", i, L.data[i]);
    }
    return 0;
}

2、动态分配

C语言知识

1、malloc 是一个系统函数,它是 memory allocate 的缩写。其中memory是“内存”的意思,allocate是“分配”的意思。顾名思义 malloc 函数的功能

就是“分配内存”。要调用它必须要包含头文件

malloc 函数的返回值是一个地址,这个地址就是动态分配的内存空间的起始地址。如果此函数未能成功地执行,如内存空间不足,则返回空指针

NULL。

int *p = (int *)malloc(4);

请求系统分配 4 字节的内存空间,并返回第一字节的地址,然后赋给指针变量 p。当用 malloc 分配动态内存之后,上面这个指针变量 p 就被初始

化了。

2、sizeof(x) 计算变量x的长度。

int *p = malloc(sizeof(int));

sizeof(int)的值是 int 型变量所占的字节数,这样就能很好地表示当前计算机中 int 型变量占几字节。这样写程序的可移植性就增强了。所以动态

内存最好是需要多少就构建多少。

3、free( p ) 释放指针p所指变量的存储空间,即彻底删除一个变量

前面定义了一个指向动态内存的指针变量 p:

int *p = malloc(sizeof*(int));

前面讲过,动态分配的内存空间是由程序员手动编程释放的。那么怎么释放呢?用 free 函数。

free 函数无返回值,它的功能是释放指针变量 p 所指向的内存单元。此时 p 所指向的那块内存单元将会被释放并还给操作系统,不再归它使用。

操作系统可以重新将它分配给其他变量使用。

需要注意的是,释放并不是指清空内存空间,而是指将该内存空间标记为 “可用”状态,使操作系统在分配内存时可以将它重新分配给其他变量使用。

注意:

只有动态创建的内存才能用 free 把它释放掉,静态内存是不能用free释放的。静态内存只能由系统释放。

代码实现:

#include 
#include 
#define InitSize 10 //顺序表的初始长度
typedef struct SqList
{
    /* data */
    int *data; //用静态的“数组”存放数据元素
    int MaxSize; //定义最大长度
    int length; //顺序表的当前长度
};

void InitList(SqList &L)
{
    //用malloc函数申请一片连续的存储空间
    L.data = (int *)malloc(InitSize*sizeof(int));
    L.length = 0;
    L.MaxSize = InitSize;
}

void IncreaseSize(SqList &L, int len) {
    int *p = L.data;
    L.data = (int *)malloc((L.MaxSize + len)*sizeof(int));
    for (int i = 0; i < L.length; i++)
    {
        /* code */
        L.data[i] = p[i];  //将数据复制到新区域
    }
    L.MaxSize = L.MaxSize + len; //顺序表最大长度增加len
    free(p);  //释放原来的内存空间
    
}
int main()
{
    SqList L;    //声明一个顺序表
    InitList(L); //初始化
    //插入元素
    IncreaseSize(L, 5);
    return 0;
}

3、顺序表特点

1、随机访问,即可以在 O(1) 时间内找到第 i 个元素。(代码实现:data[i-1];静态分配、动态分配都一样 )
2、存储密度高,每个节点只存储数据元素
3、拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)
4、插入、删除操作不方便,需要移动大量元素

2.2 基本操作的实现

1、插入

ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e

代码实现:

#include
#include
#define MaxSize 10
using namespace std;
typedef struct
{
    /* data */
    int data[MaxSize];
    int length;
}SqList;

/**
 * 初始化
 */
void InitList(SqList &L)
{
    L.length = 0; //顺序表初始长度为0;
}

/**
 * 插入顺序表
 */
bool ListInsert(SqList &L, int i, int e) {
    //判断i的范围是否有效
    if (i < 1 || i > L.length + 1)
    {
        return false;
        /* code */
    }
    //判断当前存储空间是否已满,不能插入
    if (L.length >= MaxSize) 
    {
        /* code */
        return false;

    }
    //将第i个元素及以后的元素后移
    for (int j = L.length; j >= i; j--)
    {
        /* code */
        L.data[j] = L.data[j - 1];
    }
    //在位置i处放入e
    L.data[i - 1] = e;
    L.length++;  //长度++
    return true;
    
}

/**
 * 打印输出
 */ 
void printList(SqList L) {
    cout << "长度length = " << L.length << endl;
    cout << "数据为:" << endl;
    for (int i = 0; i < L.length; i++)
    {
        /* code */
        cout << "data[" << i << "] = " << L.data[i] << endl;
        
    }
    
}
int main() {
    SqList L;
    InitList(L);
    //赋值
    for (int i = 0; i < 5; i++)
    {
        /* code */
        L.data[i] = i;
        L.length++;
    }
    printList(L);
    bool flag = ListInsert(L, 2, 6);
    cout << flag << endl;
    printList(L);
    return 0;
}
长度length = 5
数据为:
data[0] = 0
data[1] = 1
data[2] = 2
data[3] = 3
data[4] = 4
1
长度length = 6
数据为:
data[0] = 0
data[1] = 6
data[2] = 1
data[3] = 2
data[4] = 3
data[5] = 4

时间复杂度

1、最好情况: 新元素插入到表尾,不需要移动元素

i = n+1,循环0次;最好时间复杂度 = O(1)

2、最坏情况: 新元素插入到表头,需要将原有的 n 个元素全都向后移动

i = 1,循环 n 次;最坏时间复杂度 = O(n)

3、平均情况: 假设新元素插入到任何一个位置的概率相同,即 i = 1,2,3, … , length+1 的概率都是 p = 1 ( n + 1 ) p = \frac{1}{(n+1)} p=(n+1)1

i = 1,循环 n 次;i = 2 时,循环 n - 1 次;i = 3,循环 n-2 次 …… i = n + 1时,循环0次

平均循环次数 = n p + ( n − 1 ) p + ( n − 2 ) p + … … + 1 ⋅ p = n ( n + 1 ) 2 ∗ 1 ( n + 1 ) = n 2 平均循环次数 = np + (n-1)p + (n-2)p + …… + 1⋅p = \frac{n(n+1)}{2} * \frac{1}{(n+1)} = \frac{n}{2} 平均循环次数=np+(n1)p+(n2)p+……+1p=2n(n+1)(n+1)1=2n

平均时间复杂度 = O(n)

2、删除

ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值

代码实现:

#include
#include
#define MaxSize 10
using namespace std;
typedef struct
{
    /* data */
    int data[MaxSize];
    int length;
}SqList;

/**
 * 初始化
 */
void InitList(SqList &L)
{
    L.length = 0; //顺序表初始长度为0;
}

/**
 * 删除顺序表
 */
bool ListDelete(SqList &L, int i, int &e) {
    //判断i的范围是否有效
    if (i < 1 || i > L.length + 1)
    {
        return false;
        /* code */
    }
    e = L.data[i - 1];
    //将第i个元素及以后的元素前移
    for (int j = i; j < L.length; j++)
    {
        /* code */
        L.data[j - 1] = L.data[j];
    }
    L.length--;  //长度--
    return true;
    
}

/**
 * 打印输出
 */ 
void printList(SqList L) {
    cout << "长度length = " << L.length << endl;
    cout << "数据为:" << endl;
    for (int i = 0; i < L.length; i++)
    {
        /* code */
        cout << "data[" << i << "] = " << L.data[i] << endl;
        
    }
    
}
int main() {
    SqList L;
    InitList(L);
    //赋值
    for (int i = 0; i < 5; i++)
    {
        /* code */
        L.data[i] = i;
        L.length++;
    }
    int e;
    printList(L);
    ListDelete(L, 4, e);
    printList(L);
    cout << "删除的数字为 " << e << " " << endl;
    return 0;
}

长度length = 5
数据为:
data[0] = 0
data[1] = 1
data[2] = 2
data[3] = 3
data[4] = 4
长度length = 4
数据为:
data[0] = 0
data[1] = 1
data[2] = 2
data[3] = 4
删除的数字为 3

时间复杂度

1、最好情况: 删除表尾元素,不需要移动其他元素

i = n,循环 0 次;最好时间复杂度 = O(1)

2、最坏情况: 删除表头元素,需要将后续的 n-1 个元素全都向前移动

i = 1,循环 n-1 次;最坏时间复杂度 = O(n)

3、平均情况: 假设删除任何一个元素的概率相同,即 i = 1,2,3, … , length 的概率都是 p = 1 n p = \frac{1}{n} p=n1

i = 1,循环 n-1 次;i=2 时,循环 n-2 次;i=3,循环 n-3 次 …… i =n 时,循环0次

平均循环次数 = ( n − 1 ) p + ( n − 2 ) p + … … + 1 ⋅ p = n ( n − 1 ) 2 ∗ 1 n = ( n − 1 ) 2 平均循环次数 = (n-1)p + (n-2)p + …… + 1⋅p = \frac{n(n-1)}{2} * \frac{1}{n} = \frac{(n-1)}{2} 平均循环次数=(n1)p+(n2)p+……+1p=2n(n1)n1=2(n1)

平均时间复杂度 = O(n)

3、查找

(1)按位查找

GetElem(L,i): 按位查找操作。获取表 L 中第 i 个位置的元素的值。

代码实现:

#include 
#include 
#include 
#define InitSize 10
using namespace std;
typedef struct
{
    /* data */
    int *data; //动态分配
    int length;
    int MaxSize;
} SqList;

/**
 * 初始化
 */
void InitList(SqList &L)
{
    L.data = (int *)malloc(InitSize*sizeof(int));
    L.MaxSize = InitSize;
    L.length = 0; //顺序表初始长度为0;
}

/**
 * 按位查找顺序表
 */
int ListSearchBySite(SqList L,  int i)
{
    cout << "查找第 " << i << " 位的值为:";
    return  L.data[i - 1];
}

/**
 * 打印输出
 */
void printList(SqList L)
{
    cout << "长度length = " << L.length << endl;
    cout << "数据为:" << endl;
    for (int i = 0; i < L.length; i++)
    {
        /* code */
        cout << "data[" << i << "] = " << L.data[i] << endl;
    }
}

int main()
{
    SqList L;
    InitList(L);
    //赋值
    for (int i = 0; i < 5; i++)
    {
        /* code */
        L.data[i] = i;
        L.length++;
    }
    printList(L);
    cout << ListSearchBySite(L, 3) << endl;
    return 0;
}

长度length = 5
数据为:
data[0] = 0
data[1] = 1
data[2] = 2
data[3] = 3
data[4] = 4
查找第 3 位的值为:2

时间复杂度

由于顺序表的各个数据元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第 i 个元素——“随机存取”特性

时间复杂度 = O(1)

(2)按值查找

LocateElem(L,e): 按值查找操作。在表L中查找具有给定关键字值的元素。

代码实现:

#include 
#include 
#include 
#define InitSize 10
using namespace std;
typedef struct
{
    /* data */
    int *data;
    int length;
    int MaxSize;
} SqList;

/**
 * 初始化
 */
void InitList(SqList &L)
{
    L.data = (int *)malloc(InitSize * sizeof(int));
    L.MaxSize = InitSize;
    L.length = 0; //顺序表初始长度为0;
}

/**
 * 按值查找顺序表
 */
int ListSearchByValue(SqList L, int e)
{
    cout << "查找值为 " << e << " 的位置为:";
    for (int i = 0; i < L.length; i++)
        if (L.data[i] == e)
            return i + 1; //数组下标为i的元素值等于e,返回其位序i+1
    return 0;             //退出循环,说明查找失败
}


/**
 * 打印输出
 */
void printList(SqList L)
{
    cout << "长度length = " << L.length << endl;
    cout << "数据为:" << endl;
    for (int i = 0; i < L.length; i++)
    {
        /* code */
        cout << "data[" << i << "] = " << L.data[i] << endl;
    }
}

int main()
{
    SqList L;
    InitList(L);
    //赋值
    for (int i = 0; i < 5; i++)
    {
        /* code */
        L.data[i] = i;
        L.length++;
    }
    printList(L);
    cout << ListSearchByValue(L, 1) << endl;
    return 0;
}

长度length = 5
数据为:
data[0] = 0
data[1] = 1
data[2] = 2
data[3] = 3
data[4] = 4
查找值为 1 的位置为:2

时间复杂度

1、最好情况: 目标元素在表头循环1次;最好时间复杂度 = O(1);

2、最坏情况: 目标元素在表尾循环 n 次;最坏时间复杂度 = O(n);

3、平均情况: 假设目标元素出现在任何一个位置的概率相同,都是 1 n \frac{1}{n} n1

平均循环次数 = n p + ( n − 1 ) p + ( n − 2 ) p + … … + 1 ⋅ p = n ( n + 1 ) 2 ∗ 1 n = ( n + 1 ) 2 平均循环次数 = np + (n-1)p + (n-2)p + …… + 1⋅p = \frac{n(n+1)}{2} * \frac{1}{n} = \frac{(n+1)}{2} 平均循环次数=np+(n1)p+(n2)p+……+1p=2n(n+1)n1=2(n+1)

平均时间复杂度 = O(n)

附上篇时间复杂度分析博文
数据结构-绪论(二)---- 时间复杂度分析

你可能感兴趣的:(C/C++数据结构,数据结构,c++,c语言,链表,线性表)