데이터 구조: 선형 테이블 구문 분석

목차

1. 서열표

1.1 정적 시퀀스 테이블

SeqList.h

1.2 동적 시퀀스 테이블

1.2.1 주요 기능

1.2.2 주요 작업

1.2.3 코드 구현

둘, 연결리스트

2.1 개념

2.2 주요 기능

2.3 일반적인 유형

2.4 장점과 단점

이점

결점

2.5 코드 구현

링크리스트.h

링크리스트.cpp


선형 목록은 동일한 속성을 가진 n개의 데이터 요소로 구성된 유한한 시퀀스입니다. 선형 테이블은 실제로 널리 사용되는 데이터 구조이며 일반적인 선형 테이블: 순차 목록, 연결 목록, 스택, 큐, 문자열...

선형테이블은 논리적 구조상 선형구조 , 즉 연속적인 직선이다.

그러나 물리적 구조 (실제 저장)가 반드시 연속적인 것은 아니며, 선형 테이블을 물리적으로 저장하는 경우 대개 배열 (순차 리스트)과 연결 구조 (연결 리스트)의 형태로 저장된다.

1. 서열표

시퀀스 테이블은 데이터 요소들이 연속적인 물리적 주소를 갖는 저장 단위에 순차적으로 저장되는 선형 구조로, 일반적으로 어레이 형태로 저장된다.

참고: 여기서는 어레이를 사용하며 내부적으로 스토리지를 건너뛸 수는 없지만 지속적인 스토리지가 필요합니다.

125efb132d77475882b2a8575fac8588.png

참고 : 배열 첨자(0부터 시작) 사용에 주의하세요. 그림의 내용은 요소의 예, 즉 저장된 내용입니다!

1.1 정적 시퀀스 테이블

정적 시퀀스 테이블은 고정된 길이를 특징으로 하며 일단 생성되면 길이가 변경되지 않습니다.

장점 은 요소 접근이 빠르고, 인덱스를 통해 요소에 직접 접근이 가능하며, 시간 복잡도가 O(1)이라는 점이다.

단점 은 요소를 삽입하고 삭제하는 작업이 느리고 요소를 이동해야 하며 시간 복잡도가 O(n)이라는 점입니다.

참고: 정적 시퀀스 테이블을 사용할 때는 오버플로나 너무 많은 공간 낭비를 방지하기 위해 요소 수를 합리적으로 추정하는 것이 필요합니다.

SeqList.h

#pragma one //预处理指令,用于确保头文件在编译过程中只被包含一次,防止重复定义和潜在的编译错误
constexpr int MAX_SIZE = 100; //声明一个编译时常量用于确定数组长度
typedef int SqDataType;	//便于对数据类型的更改(后续可使用模板优化)

class SeqList{
private:
	SqDataType _data[MAX_SIZE];	//静态数组
   	size_t _size;				//数据元素数量
}

참고: 여기서는 정적 시퀀스 테이블만 정의하고 구현하지 않았습니다. 상대적으로 간단하고 사용 빈도가 낮기 때문입니다.

1.2 동적 시퀀스 테이블

정적 시퀀스 테이블과 달리 동적 시퀀스 테이블의 길이는 요소 삽입 및 삭제와 같은 작업을 수용하기 위해 런타임 시 동적으로 조정될 수 있습니다.

동적 시퀀스 테이블에서는 일반적으로 시퀀스 테이블의 저장 공간을 관리하기 위해 동적 메모리 할당이 사용됩니다.

1.2.1 주요 기능

  1. 가변 길이: 동적 시퀀스 테이블의 길이는 필요에 따라 동적으로 확장되거나 축소될 수 있습니다. 요소 수가 현재 용량을 초과하면 더 많은 요소를 수용할 수 있도록 메모리가 자동으로 확장될 수 있습니다.

  2. 동적 메모리 관리: 동적 메모리 할당을 사용하여 시퀀스 테이블의 저장 공간을 동적으로 관리합니다. 메모리를 적용하고 해제함으로써 동적 시퀀스 테이블은 수요에 따라 용량을 조정할 수 있습니다.

  3. 유연성: 가변 길이로 인해 동적 시퀀스 테이블은 다른 요소를 이동하지 않고도 삽입, 삭제, 수정 등의 작업을 유연하게 수행할 수 있습니다.

1.2.2 주요 작업

  1. 시퀀스 테이블 생성: 일정량의 메모리 공간을 동적으로 할당하고 시퀀스 테이블의 관련 속성을 초기화합니다.

  2. 요소 삽입: 지정된 위치에 요소를 삽입합니다.현재 용량이 부족할 경우 메모리 공간을 확장해야 합니다.

  3. 요소 삭제 : 지정된 위치의 요소를 삭제하며, 삭제 후 용량이 너무 커지면 메모리 공간이 줄어들 수 있습니다.

  4. 요소 찾기: 인덱스 위치나 요소 값에 따라 검색하여 해당 요소를 반환합니다.

  5. 요소 수정: 인덱스 위치나 요소 값에 따라 지정된 위치의 요소 값을 수정합니다.

  6. 길이 가져오기: 시퀀스 테이블의 요소 수를 반환합니다.

  7. 시퀀스 테이블 순회: 시퀀스 테이블의 모든 요소를 ​​순서대로 출력합니다.

1.2.3 코드 구현

SeqList.h

#pragma one
#include<iostream>

typedef int SqDataType;
class SeqList {
private:
    SqDataType* _array;  // 存储数据的数组指针
    size_t _size;    // 当前元素个数
    size_t _capacity;  // 当前容量
public:
    SqList();	//构造函数
    ~SqList();	//析构函数
    void checkCapacity();	//检查容量
    void push_back(SqDataType value);	//尾部插入
    void push_front(SqDataType value);	//头部插入
    void pop_back();	//尾部删除
    void pop_front();	//头部删除
    SqDataType get(size_t index); 	//按索引位置查找
    size_t getSize();	//获取长度
    void print();	//遍历顺序表
}

SeqList.cpp

#include"SeqList.h"

SeqList::SeqList()
{
        _array = nullptr;	//对成员属性进行初始化
        _size = 0;
        _capacity = 0;
}

SeqList::~SeqList()
{
    if(_array != nullptr)
    	delete[] _array;
}

//由于头插尾插都需要检查容量,便提取出此函数用于检查容量
void checkCapacity()
{
    //对初次插入和后续插入进行扩容处理
    //因为初次进入_size和_capacity都为0,判断为满,进行扩容
    if (_size == _capacity) {
        if (_capacity == 0)
            _capacity = 1;
        else
            _capacity *= 2;

        //开辟新空间并进行数据移动
        int* newArray = new SqDataType[_capacity];
        for (int i = 0; i < _size; i++) {
            newArray[i] = _array[i];
        }

        //将旧空间进行释放,避免内存泄漏
        delete[] _array;
        _array = newArray;
    }    
}

//尾部插入数据
void push_back(int value) {
	checkCapacity();

    //此处使用_size++需要读者结合数组下标特性进行理解
    _array[_size++] = value;
}

//尾部删除
void pop_back()
{
    //只有在有容量的情况下才能进行删除
    if (_size > 0)
        _size--;
}

//头部插入
void push_front(SqDataType value)
{
	checkCapacity();
    
    //在头部插入数据需要将后续数据全部向后挪动
    for(size_t index = size; index > 0; index--)
    	_array[index] = _array[index - 1];
    
    //挪动数据后将数据插入头部更新size
    _array[0] = value;
    _size++;
}

//头部删除
void pop_front()
{
    if(_size != 0)
    {
        for(size_t index = 0; index < _size - 1; index++)
            _array[index] = _array[index + 1];
        
        _size--;
    }
}

//按索引查找
SqDataType get(size_t index)
{
    if(index >= 0 && index < _size)
        return _array[index];
    else
        throw std::out_of_range("Index out of range.");
}

//获取容量
size_t getSize()
{
    return _size;
}

//便利打印顺序表
void Print()
{
    for(size_t index = 0; index < _size; index++)
        std::cout << _array[index] << " ";
    std::cout << std::endl;
}

둘, 연결리스트

2.1 개념

연결리스트(Linked List)는 물리적 구조에서 비연속적이고 비순차적인 저장구조로, 연결리스트에 있는 포인터의 연결순서를 통해 데이터 요소의 논리적 순서를 구현한다.

연결된 목록은 일련의 노드로 구성된 일반적인 선형 데이터 구조이며, 각 노드에는 데이터 요소와 다음 노드에 대한 포인터가 포함되어 있습니다.

인접한 노드들은 배열처럼 메모리에 계속해서 저장되는 것이 아니라 포인터로 연결됩니다.

2.2 주요 기능

  1. 역학: 고정된 메모리 공간을 미리 할당하지 않고도 연결된 목록의 길이를 동적으로 늘리거나 줄일 수 있습니다.
  2. 유연성: 노드가 포인터로 연결되어 있으므로 다른 노드를 이동하지 않고도 노드를 쉽게 삽입, 삭제, 수정할 수 있습니다.
  3. 저장 효율성: 배열과 비교하여 연결 목록은 각 노드가 다음 노드를 가리키는 추가 포인터가 필요하기 때문에 저장 공간 활용도가 낮습니다.

2.3 일반적인 유형

단일 연결 목록: 각 노드에는 다음 노드를 가리키는 포인터가 하나만 있습니다. 마지막 노드의 포인터는 연결리스트의 끝을 나타내는 nullptr입니다.

deaaa4d23d7640afb0596344fd1db1f4.png

단일 연결 리스트
 

이중 연결 목록: 각 노드에는 두 개의 포인터가 있으며, 하나는 이전 노드를 가리키고 다른 하나는 다음 노드를 가리킵니다. 이런 방식으로 연결된 리스트는 양방향으로 탐색할 수 있지만, 단일 연결 리스트에 비해 이중 연결 리스트는 추가 포인터를 저장하기 위해 더 많은 메모리 공간이 필요합니다.

5c6bd2b367da4adba8f5aff36bd4c6d2.png

이중
연결리스트

2.4 장점과 단점

이점

  1. 역학: 연결된 목록의 길이는 동적으로 늘어나거나 줄어들 수 있으며, 이는 노드를 자주 삽입하고 삭제해야 하는 시나리오에 적합합니다.
  2. 유연성: 연결된 목록의 노드는 다른 노드를 이동하지 않고도 쉽게 삽입, 삭제 및 수정할 수 있으므로 작업이 더욱 유연해집니다.

결점

  1. 낮은 랜덤 액세스 효율성: 연결 리스트의 요소는 배열과 같은 인덱스를 통해 빠르게 접근할 수 없으며 헤드 노드에서 연결 리스트를 순회해야 하며 시간 복잡도는 O(n)입니다.
  2. 추가 메모리 오버헤드: 각 노드에는 다른 노드에 연결하기 위한 추가 포인터가 필요하며, 이로 인해 특정 양의 메모리 오버헤드가 추가됩니다.

2.5 코드 구현

여기에 구현된 것은 선행 노드가 없는 엔트리 레벨 단일 연결 리스트입니다. 여기 코드를 이해할 수 있다면 다음 독자들도 스스로 연결 리스트의 변형을 완성할 수 있습니다. 저자는 또한 다양한 유형을 소개하는 기사를 게시할 것입니다. 연결리스트를 코드로 구현해 보세요.

링크리스트.h

#ifndef LINKEDLIST_H
#define LINKEDLIST_H

//节点类
class Node {
public:
    int data;
    Node* next;
    //初始化节点
    explicit Node(int value);
};

//链表类
class LinkedList {
private:
    Node* head;

public:
    //初始化链表
    LinkedList();
    //尾插节点
    void push_back(int value);
    //尾删节点
    void pop_back();
    //头插节点
    void push_front(int value);
    //头删节点
    void pop_front();
    //遍历打印顺序表
    void printList();
    //析构函数
    ~LinkedList();
};

#endif

링크리스트.cpp


#include "LinkList.h"
#include <iostream>

Node::Node(int value) {
    data = value;
    next = nullptr;
}

LinkedList::LinkedList() {
    head = nullptr;
}

void LinkedList::push_back(int value) {
    Node* newNode = new Node(value);

    //遍历寻求到最后节点,再添加节点即可
    if (head == nullptr) {
        head = newNode;
    } else {
        Node* current = head;
        while (current->next != nullptr) {
            current = current->next;
        }
        current->next = newNode;
    }
}

void LinkedList::push_front(int value) {
    Node* newNode = new Node(value);

    //区别在于是否需要修改头指针
    if (head == nullptr) {
        head = newNode;
    } else {
        newNode->next = head;
        head = newNode;
    }
}

void LinkedList::printList() {
    Node* current = head;
    while (current != nullptr) {
        std::cout << current->data << " ";
        current = current->next;
    }
    std::cout << std::endl;
}

void LinkedList::pop_back() {
    if (head != nullptr) {
        Node* current = head;
        Node* pre = nullptr;

        // 处理只有一个节点的情况
        if (head->next == nullptr) {
            delete head;
            head = nullptr;
        } else {
            // 遍历链表找到最后一个节点
            while (current->next != nullptr) {
                pre = current;
                current = current->next;
            }

            // 删除最后一个节点
            pre->next = nullptr;
            delete current;
        }
    }
}

void LinkedList::pop_front() {
    //无节点不能删除
    //同时需要判断是否只有一个节点,需要特殊处理
    if(head != nullptr)
    {
        if(head->next != nullptr)
        {
            Node* temp = head;
            head = temp->next;
            delete temp;
        }
        else
        {
            Node* temp = head;
            head = nullptr;
            delete temp;
        }
    }
}

LinkedList::~LinkedList() {
    //遍历链表中所有节点,逐个进行释放内存
    Node* current = head;
    Node* pre = nullptr;
    while(current != nullptr)
    {
        pre = current;
        current = current->next;
        delete pre;
    }
}

설명하다:

연결된 목록을 만들려면 먼저 헤드 포인터, 즉 항상 헤드 노드 또는 nullptr을 가리키는 노드 노드 포인터를 내부적으로 유지 관리하는 위에서 언급한 LinkList 클래스가 필요합니다.

연결된 리스트를 수정하려면 해당 주소를 사용하여 수정해야 하기 때문에 헤드 노드의 포인터를 얻은 후 헤드 노드는 다음 노드의 포인터도 저장합니다.유추하면 연결된 전체를 찾을 수 있습니다. 목록을 작성하고 이에 대한 다양한 작업을 수행합니다.

독자는 위의 코드를 기반으로 자신만의 테스트를 설계하고, 간단한 단방향 연결 리스트를 경험하고, 이를 기반으로 기능을 확장할 수 있습니다.

이것으로 이번 글은 끝이고, 앞으로 다양한 변형 연결 리스트에 대한 글이 나올 예정입니다!

추천

출처blog.csdn.net/weixin_57082854/article/details/131676696