数据结构学习:线性表的顺序表示和实现1

发布时间:2024年01月20日

1、线性表的定义与概念。

线性表是计算机科学中一种常见的数据结构,是由一组按照顺序排列的元素组成的数据集合。线性表中的元素之间存在一对一的关系即每个元素都有唯一的前驱和后继

线性表可以用来表示各种实际问题中的数据集合,例如存储一组学生信息、保存一段文字等。线性表的特点是元素之间的顺序是确定的,可以通过索引访问和操作元素。

线性表有两种常见的实现方式:顺序存储和链式存储。

  • 顺序存储:线性表的元素在内存中连续存储,可以通过下标直接访问任意位置的元素,查找和插入元素的时间复杂度为O(1)。但是插入和删除操作需要移动其他元素,时间复杂度为O(n)。
  • 链式存储:线性表的元素通过指针连接起来,每个元素包含一个数据域和一个指针域,指针指向下一个元素。链式存储不需要连续的内存空间,插入和删除元素的时间复杂度为O(1),但是查找元素需要遍历链表,时间复杂度为O(n)。

线性表的常见操作包括插入元素、删除元素、查找元素、获取线性表长度等。线性表还可以进行合并、拆分、排序等操作,具体根据实际需求而定。

2、线性表的顺序存储表示。

线性表的顺序存储表示是将线性表中的元素依次存放在一段连续的存储空间中,通常使用数组来实现。线性表的长度由元素个数决定,每个元素占用一个存储单元,可以通过下标直接访问任意位置的元素。

例如,定义一个包含10个整数的线性表,可以使用以下代码进行顺序存储表示:

#define MAXSIZE 10  // 定义线性表的最大长度
typedef struct{
    int data[MAXSIZE];  // 存储线性表元素的数组
    int length;  // 线性表的实际长度
}SqList;  // 定义顺序存储结构体

// 初始化线性表
void InitList(SqList &L)
{
    for(int i = 0; i < MAXSIZE; i++){
        L.data[i] = 0;
    }
    L.length = 0;
}

// 插入元素
bool InsertList(SqList &L, int pos, int elem)
{
    if(pos < 1 || pos > L.length + 1){
        return false;
    }
    if(L.length >= MAXSIZE){
        return false;
    }
    for(int i = L.length; i >= pos; i--){
        L.data[i] = L.data[i-1];
    }
    L.data[pos-1] = elem;
    L.length++;
    return true;
}

// 删除元素
bool DeleteList(SqList &L, int pos)
{
    if(pos < 1 || pos > L.length){
        return false;
    }
    for(int i = pos; i < L.length; i++){
        L.data[i-1] = L.data[i];
    }
    L.length--;
    return true;
}

// 查找元素
int SearchList(SqList L, int elem)
{
    for(int i = 0; i < L.length; i++){
        if(L.data[i] == elem){
            return i+1;
        }
    }
    return 0;
}

在上述代码中,用SqList结构体表示线性表,其中data数组存储元素,length表示线性表的实际长度。InitList函数用于初始化线性表,将所有元素赋值为0;InsertList函数用于插入元素,需要指定插入位置和元素值;DeleteList函数用于删除元素,需要指定删除位置;SearchList函数用于查找元素,返回元素所在位置。

注意:1、线性表的顺序存储表示和链表表示不一样,顺序存储表示一般不需要什么地址,只需要知道基地址那么后面的也就知道了,所以顺序存储线性表的组成就是由存储元素的数组和记录线性表实际长度的变量组成,即一个数据域,一个存储元素的数值域,就和链表有区别,链表是由数据域和指针域组成,有指针就是因为不是连续的地址值,是指定的,指定哪个地址就是哪个地址,所以一般看到书面表示的链表都是有箭头的,其实那就是抽象具体话的指针,让我们更好的理解链表的运行过程;而顺序存储书面表示就是一个连续的存储条,示意着每个数据元素存储的地址是连续的,所以这样书面表示就更直观的看出顺序存储的特点。

2、就算开辟的空间是连续的,但是你存储元素的时候不按照连续的来存,在中间隔了几个空位再存储,那么这种存储就不是一个线性表的顺序存储结构。顺序存储结构一定是要连续的每个地址存储,而不是隔着存储也算的,这点可能会在题里面考到,需要注意一下。

c++在线性表顺序存储里面的补充说明:

1、元素类型说明。(数组定义)

数组静态分配

typedef struct{
    ElemType data[Maxsize]
    int length;
}SqList;  //顺序表类型
数组动态分配

typedef struct{
ElemType *data; //这里获取的是数组的首地址,即数组第0号位元素的地址。
int length;

}SqList;//顺序表类型
c语言里面为线性表开辟空间的表示形式

SqList;
L.data=(ElemType*)malloc(sizeof(ElemType)*Maxsize);


//这个东西一般不好用,而且操作难,效率低,所以当作了解,毕竟考试就要考这个东西

malloc函数后面是要开辟的空间内存一共的字节数,malloc函数左边是强制类型转换。这是什么意思呢,意思就是你要存什么类型的变量,是?int,char,double等一般类型变量,还是你自己定义的结构体类型变量,是什么类型,就把那个ElemType那个地方替换成那个,注意要加括号的,强制类型转换都是要加的。

? ?后面那个* 符号是什么意思呢,意思是你开辟了一个空间,开辟来想用的话,那么你就要用指针指向这个内存空间的首地址,然后你才能在这个内存空间里面操作。所以这个*就相当于是定义了一个ElemType类(看实际情况来替换)的指针,指向malloc函数开辟的空间,然后在里面操作。

总结:malloc(m)函数,开辟m字节长度的地址空间,并返回这段空间的首地址。

sizeof(x)运算,计算变量x的长度。

free(p)函数,释放指针p所指变量的存储空间,即彻底删除一个变量。(c里面特有的释放内存的方式,也是和malloc函数一样,存在诸多问题,没有c++的好,后面就会讲c++的,到时候可以比较一下来看的)

需要加载头文件:<stdlib.h>

注意哦,这三样总结是在C里面表现方式里面的,意思是c里面才是这样,c++不是这样哦

2、c++中的动态存储分配(就是上面说的那个为线性表开辟空间的那个,只不过是c++里面的形式)

C++中的动态存储分配是通过使用newdelete操作符来实现的。动态存储分配允许程序在运行时动态地分配内存空间,并在使用完毕后释放该空间,以供其他用途。

  1. 动态内存分配(Allocation):通过new操作符来请求一块指定大小的内存空间。例如,可以使用int* p = new int;来分配一个整数类型的内存空间,并将其地址赋给指针p

  2. 内存使用:可以像使用普通变量一样使用动态分配的内存空间。例如,可以通过*p = 10;将值10存储到所分配的内存空间中。

  3. 动态内存释放(Deallocation):通过delete操作符释放动态分配的内存空间。例如,可以使用delete p;来释放先前分配的内存空间。

需要注意以下几点:

  • 使用new分配的内存空间必须使用delete进行释放,否则会导致内存泄漏。
  • 在释放内存之前,确保不再需要使用该内存空间,否则会导致使用已释放内存的未定义行为。
  • 可以使用delete[]操作符释放通过new[]分配的数组内存空间。

动态存储分配在需要根据程序需求来动态分配内存空间的情况下非常有用。然而,需要小心使用,以避免内存泄漏和悬空指针等问题。

3、c++里面动态存储的分析(举个例子)

例如:int *p1=new int(10);

  1. new int(10):使用new操作符动态分配一个整数类型的内存空间,并将值10存储在该内存空间中。这里的数字10是一个初始化的值,表示将分配的内存空间初始化为10。

  2. int *p1:声明一个指向整数类型的指针变量p1用于存储动态分配内存空间的地址

综合起来,这句话的作用是:动态分配一个整数类型的内存空间,并将其中的值初始化为10,并将该内存空间的地址存储在指针变量p1中。

通过*p1可以访问和修改所分配的内存空间中的值,例如*p1 = 20;将内存空间中的值修改为20。(注意,这里说的可以把10这个值修改成20,前面已经声明了p1是指针变量了,如今想要修改首地址的值,就要在p1前面加一个解引用指针的符号*,然后就有了*p1=20,然后成功修改了值的这个结果,如果你前面没有声明或者说了是指针变量,那么这个值就是修改不了的,因为*这个符号刚开始出现他表示的意思就是一个指针变量,指针变量就是存地址的特殊变量,第一个后面的*符号就因情况而定,在这里就是解引用指针,获取地址里面的值,然后修改的,如果你听不懂,可以看看我前面讲解引用指针的知识点,看懂了再回来看这里,也不迟)记得在不需要使用该内存空间时,使用delete p1;释放所分配的内存空间,以避免内存泄漏。

4、new分配空间和malloc函数分配空间的区别。

new运算符和malloc()函数都可以用于分配内存空间,但它们有以下几个不同点:

  1. 类型安全:new运算符是C++的一部分,它会根据所指定的类型进行内存分配,并返回对应类型的指针。因此,它是类型安全的,不需要显式地进行类型转换。malloc()函数是C语言中的函数,它分配的是void*类型的内存空间,需要显式地进行类型转换。

  2. 构造函数和析构函数的调用:使用new运算符分配的内存空间会自动调用对象的构造函数进行初始化,并在释放时调用析构函数进行清理。malloc()函数只负责分配内存空间,不会自动调用构造函数和析构函数。

  3. 大小计算:使用new运算符时,编译器会根据类型信息自动计算所需的大小。malloc()函数需要手动指定所需的字节数。

  4. 内存对齐:new运算符会根据所需类型的对齐要求,进行内存对齐。malloc()函数分配的内存空间没有对齐保证,可能需要手动进行对齐操作。

? ?总的来说,new运算符更适用于C++代码,它提供了更高级的功能,并且与对象的构造和析构过程紧密关联。malloc()函数则更适用于C代码,它是一个通用的内存分配函数,不具备类型安全和自动调用构造析构函数等特性。在C++中,推荐使用new运算符和delete操作符来进行动态内存分配和释放。

5、new运算符为结构体类型分配内存空间的时候是怎么实现的

当使用new运算符为结构体类型分配内存空间时,编译器会执行以下步骤:

  1. 首先,编译器会根据结构体类型的定义,计算出所需的内存空间大小。这包括结构体内部的成员变量以及可能存在的额外内存对齐需求。

  2. 接下来,编译器会调用相应类型的构造函数(如果有的话),对分配的内存空间进行初始化。构造函数负责设置结构体中的成员变量的初始值或执行其他必要的初始化操作。如果结构体没有定义构造函数,则会执行默认的构造函数,该构造函数将成员变量初始化为默认值。

  3. 最后,new运算符返回一个指向分配的内存空间的指针,该指针的类型与结构体类型相匹配。

例如,考虑以下结构体类型的定义:

struct Person {
    int age;
    char name[20];
};

?使用new运算符分配内存空间的示例代码如下:

Person* p = new Person();

?

在上述代码中,new Person()会动态分配sizeof(Person)大小的内存空间,然后调用Person结构体的默认构造函数进行初始化。最后,返回一个指向所分配内存空间的指针,并将其赋值给指针变量p

需要注意的是,使用new运算符分配的内存空间应该在不再使用时通过delete运算符进行释放,以避免内存泄漏。例如:

delete p;

?这样可以释放先前分配的内存空间,并调用Person结构体的析构函数(如果有定义的话)进行清理操作。这就是c++里面分配空间的使用以及释放内存的方式,很明显要更加简洁和高效安全,所以要使用这些的时候还是尽量选择c++的方式为好。

?

6、c里面的参数传递的例子(这一部分方便考试的人复习,一般写代码的时候不会用这些)

形参变化实参也跟着变化的情况。

#include <iostream.h>

// 这是一个交换两个数值的伪代码,将就着看吧

void swap(float *m, float *n)
{
    float t;
    t = *m;
    *m = *n;
    *n = t;
}

void main()
{
    float a, b, *p1, *p2;
    cin >> a >> b;
    p1 = &a;
    p2 = &b;
    swap(p1, p2);
    cout << a << endl
         << b << endl;
}

c++版的

#include <iostream>

void swap(float &m, float &n)
{ /*这个形参前面加了&这个就表示形参改变,最后实参也会跟着改变买这种在c++里面叫做引用,其实就是我上面说的意思,只是给了他一个学术用语叫引用而已。*/
    float temp;
    temp = m;
    m = n;
    n = temp;
}

void main()
{
    float a, b;
    cin >> a >> b;
    swap(a, b);
    cout << a << endl
         << b << endl;
}

所以从上面看出,还是用c++的方式好,简单高效。

文章来源:https://blog.csdn.net/admin_zbn/article/details/135712848
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。