二、线性表(顺序表,链表)

一、线性表的基本概念

    线性表是由 n(n≥0)个相同类型的数据元素组成的有限序列,它是最基本、最常用的一种线性结构。线性表就像是一条线,不会分叉。除了第1个元素,每个元素都有唯一的直接前驱;除了最后一个元素,每个元素都有唯一的直接后继。

线性表有两种存储方式:顺序存储和链式存储。采用顺序存储的线性表被称为顺序表,采用链式存储的线性表被称为链表。
线性表是零个元素或者多个数据元素的优先序列。数据元素之间是有顺序的,数据元素的个数是有限的,数据元素的类型必须相同。

二、线性表的顺序存储(顺序表)

  顺序存储是表示线性表最简单的方式。 将线性表中的元素一个接着一个的存储在一块连续的存储取悦中,这种顺序表示的线性表也称为顺序表。

  顺序表是顺序存储方式,即逻辑上相邻的数据在计算机内的存储位置也是相邻的。在顺序存储方式中,元素存储是连续的,中间不允许有空,可以快速定位某个元素,但是插入、删除时需要移动大量元素。根据分配空间方法的不同,顺序表可以分为静态分配和动态分配两种。

2.1 顺序表的定义

/*
1. 顺序表静态定义需要两个元素: 静态定义就是提前定义好一个数组
    1. 定义数组 (最大容量)
    2. 元素长度
*/
typedef int ElemType;
MaxSize = 100;
typedef struct SqList
{
    int data[MaxSize];   // ElemType data[MaxSize];
    int length;          // 元素长度
}SqList;

 

 

 

/*
2. 顺序表的动态定义需要三个元素:
    1. 元素必须知道存储的空间首地址
    2. 元素的个数
    3. 数组容量
*/
MaxSize = 100;
struct SqList
{
    int *elem;
    int length;
}L;

2.2 顺序表的初始化

/*
    初始化是指为顺序表分配一段预定义大小的连续空间,elem记录基地址,顺序表长度为0。
*/
bool InitList(SqList &L)
{
    L.elem = new int[MaxSize];
    if(L.elem == NULL)
    {
        cout << "初始化分配失败" << endl;
        return false;
    }
    // 初始化成功
    L.length = 0;
    return true;
}

2.3 顺序表的取值

/*
    顺序表中的任何一个元素都可以立即找到,称为随机存取方式。要取第i个元素,只要i值合理,可以找到该元素,由于下标是从0开始的,因此第i个元素,其下标为i-1,即对应元素为L.elem[i-1]。
*/
if(i < L.length && i >=1)
{
    return L.elem[i-1];
}

 

2.4 顺序表的查找

/*
    在顺序表中查找一个元素e,可以从第一个元素开始顺序查找,依次比较每一个元素值,如果相等,则返回元素位置(位序,即第几个元素),如果查找整个顺序表都没找到,则返回-1。
*/
for(int i=0; i<L.length; i++)
{
    if(L.elem[i] == e)
    {
        return i+1;
    }
}
retrun -1;

2.5 顺序表的插入

/*
    在顺序表中第i个位置之前插入一个元素e,需要从最后一个元素开始,后移一位,…,直到把第i个元素也后移一位,然后把e放入第i个位置。
*/
if(i < 1 || i > L.length + 1)
{
    return false;
}
// 最后一个元素开始移动
for(int j=L.length-1; j>=i-1; j--)
{
    L.elem[j+1] = L.elem[j];
}
L.elem[i-1] = e;

2.6 顺序表的删除

/*
    在顺序表中删除第i个元素,需要把该元素暂存到变量e,然后从i+1个元素开始前移,…,直到把第n个元素也前移一位,即可完成删除操作。
*/
if(i >= 1 && i <= L.length)
{
    for(int j=i; j<=L.length-1; j++)
    {
        L.elem[j-1] = L.elem[j];
    }
}
L.length--;

2.7 顺序表的算法时间复杂度

 

2.8 顺序表的代码整合

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
#define MaxSize 100 // 最大空间

typedef struct
{
    int *elem;  // 基地址
    int length; // 顺序表的长度
}SqList;

// 初始化顺序表
bool InitList(SqList &L)
{
    L.elem = new int[MaxSize]; //为顺序表分配Maxsize个空间
    if (L.elem == NULL)
    {
        return false;
    }
    L.length = 0;  //空表长度为0
    return true;
}

// 顺序表创建
bool CreateList(SqList &L)
{
    int count, data;
    cout << "请输入元素个数: ";
    cin >> count;
    for (int i = 0; i < count; i++)
    {
        if (L.length == MaxSize)
        {
            cout << "顺序表已满" << endl;
            return false;
        }
        cout << "请输入第 " << i + 1 << " 个元素: ";
        cin >> data; // 输入一个数据元素
        L.elem[i] = data; //将数据存入第i个位置 
        L.length++;  // 顺序表长度增1 
        cout << endl;
    }
    return true;
}

// 顺序表按照位置取值
bool GetElem(SqList &L, int pos, int &ret)
{
    if (pos < 1 || pos > L.length) //判断pos值是否合理,若不合理,返回false
    {
        return false;
    }
    ret = L.elem[pos - 1]; // 第pos-1的单元存储着第pos个数据
    return true;
}

// 顺序表查找值
int LocateELem(SqList &L, int value, int &ret)
{
    for (int i = 0; i < L.length; i++)
    {
        if (L.elem[i] == value)
        {
            ret = i + 1;
            return ret;
        }
    }
    return -1;
}

// 顺序表插入元素
bool ListInsertData(SqList &L, int pos, int value)
{
    if (pos < 1 || pos > L.length + 1) // pos 值不合法
    {
        return false;
    }
    if (L.length == MaxSize) //存储空间已满
    {
        return false; 
    }
    for (int j = L.length - 1; j >= pos - 1; j--)
    {
        L.elem[j + 1] = L.elem[j]; // 从最后一个元素开始后移直到第pos个元素后移
    }
    L.elem[pos - 1] = value; //将新元素 value 放入第 pos 个位置
    L.length++;
    return true;
}

// 顺序表根据位置删除元素
bool ListDeletePos(SqList &L, int pos, int &ret)
{
    if (pos < 1 || pos > L.length)
    {
        return false;
    }
    ret = L.elem[pos - 1];
    for (int i = pos; i < L.length; i++)
    {
        L.elem[i - 1] = L.elem[i];
    }
    L.length--;
    return true;
}

// 输出顺序表
void print(SqList &L)
{
    for (int i = 0; i < L.length; i++)
    {
        cout << L.elem[i] << "  ";
    }
    cout << endl;
}

// 销毁顺序表
void DestortList(SqList &L)
{
    if (L.elem)
    {
        delete[] L.elem;
    }
    
}
int main()
{
    SqList L;
    int pos, value, ret;  // 位置,值,返回的值
    cout << "1. 初始化" << endl;
    cout << "2. 创建" << endl;
    cout << "3. 取值(按位置)" << endl;
    cout << "4. 查找(某个元素)" << endl;
    cout << "5. 插入" << endl;
    cout << "6. 删除" << endl;
    cout << "7. 输出" << endl;
    cout << "8. 销毁" << endl;
    cout << "0. 退出" << endl;
    int choose = -1;
    while (choose)
    {
        cout << "请选择操作: ";
        cin >> choose;
        switch (choose)
        {
        case 1:
            cout << "顺序表初始化" << endl;
            if (InitList(L))
            {
                cout << "顺序表初始化成功" << endl;
            }
            else
            {
                cout << "顺序表初始化失败" << endl;
            }
            break;
        case 2:
            cout << "顺序表创建" << endl;
            if (CreateList(L))
            {
                cout << "顺序表创建成功!" << endl;
            }
            else
            {
                cout << "顺序表创建失败!" << endl;
            }
            break;
        case 3:
            cout << "顺序表按位置取值, 请输入位置: ";
            cin >> pos;
            if (GetElem(L, pos, ret))
            {
                cout << "" << pos << " 个位置的元素是: " << ret << endl;
            }
            else
            {
                cout << "顺序表取值失败" << endl;
            }
            break;
        case 4:
            cout << "请输入顺序表要查找的数: ";
            cin >> value;
            if (LocateELem(L, value, ret) == -1)
            {
                cout << "查找失败!不存在: " << value << endl;
            }
            else
            {
                cout << "查找成功!在第 " << ret << " 位置" << endl;
            }
            break;
        case 5:
            cout << "请输入要插入的位置和要插入的数据元素:";
            cin >> pos >> value;
            if (ListInsertData(L, pos, value))
            {
                cout << "插入成功!" << endl;
            }
            else
            {
                cout << "插入失败!" << endl;
            }
            break;
        case 6:
            cout << "请输入要删除的位置: ";
            cin >> pos;
            if (ListDeletePos(L, pos, ret))
                cout << " 删除成功!删除的元素是" << ret << endl;
            else
                cout << "删除失败!" << endl;
            break;
        case 7:
            print(L);
            break;
        case 8:
            DestortList(L);
            break;
        default:
            break;
        }
    }

    return EXIT_SUCCESS;
}

三、线性表的链式存储(链表)

3.1 单链表

3.1.1 单链表的定义

typedef struct Lnode
{
    int data;
    struct Lnode *next;
}Lnode, *LinkList;

3.1.2 单链表的创建

3.1.2.1 头插法创建单链表

/*
    头插法每次把新结点插入到头结点之后,创建的单链表和数据输入顺序相反。
    
    修改指针的顺序原则:先修改没有指针标记的那一端。
*/
S->next = L->next;
L->next = S;

 

 

3.1.2.2 尾插法创建单链表

/*
    尾插法建表每次把新结点链接到链表的尾部,其创建的单链表和数据输入顺序一致。
    尾插法一定要有尾指针 r 指向链表最后一个节点,每次插入一个节点,r指针都要向后移动一个节点。
*/
S->next = NULL;
r->next = S;
r = S;

3.1.3 单链表的取值

/*
    单链表的取值不像顺序表那样可以随机访问任何一个元素,必须从第1个结点开始按顺序向后找,一直找到第i个结点。
*/

3.1.4 单链表的查找

/*
    在一个单链表中查找是否存在元素e,可以定义一个p 指针,指向第一个元素结点,比较p指向结点的数据域是否等于e。
*/

3.1.5 单链表的插入

/*
    如果要在第i个结点之前插入一个元素,则必须先找到第i-1个结点,
*/

3.1.6 单链表的删除

/*
    删除一个结点,实际上是把这个结点跳过去。根据单向链表向后操作的特性,要想跳过第i个结点,就必须先找到第i-1个结点。
*/

3.1.7 单链表的就地逆置

/*
    将带有头结点的单链表就地逆置,辅助空间复杂度为O(1)。
    设 p指向节点1, q指向节点2, 然后将 p 节点放在 L后面, 然后p指向2, q指向3,用头插法将 p 节点插入到 1 前面。 ......
*/

 

 

3.1.8 单链表的算法时间复杂度

3.1.9 单链表的代码整合

 

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

typedef struct Lnode
{
    int data; // 数据域
    struct Lnode *next; // 指针域
}Lnode, *LinkList;

// 单链表初始化
bool InitList(LinkList &L)
{
    L = new Lnode; //生成新结点作为头结点,用头指针L指向头结点
    if (L == NULL)
    {
        return false;
    }
    L->next = NULL; //头结点的指针域置空
    return true;
}

// 头插法创建单链表
void CreatListHead(LinkList &L)
{
    // 初始化头结点, 如果没有初始化头结点,那么如果执行两次此函数操作,链表的长度会增加
    L = new Lnode;
    L->next = NULL;
    int count;
    cout << "请输入链表节点合数: ";
    cin >> count;
    for (int i = 1; i <= count; i++)
    {
        LinkList s = new Lnode;
        cout << "请输入第 " << i << " 个节点的值: ";
        cin >> s->data;
        s->next = L->next;
        L->next = s;
    }
    cout << endl;
}

// 尾插法创建单链表
void CreatListFeet(LinkList &L)
{
    // 初始化头结点
    L = new Lnode;
    L->next = NULL;
    int count;
    LinkList s, r;
    r = L;  // 创建r节点往后移动,因为头结点是不动的。
    cout << "请输入节点的个数: ";
    cin >> count;
    for (int i = 1; i <= count; i++)
    {
        cout << "请输入第 " << i << " 个节点的值: ";
        s = new Lnode; 
        cin >> s->data;
        s->next = NULL;
        r->next = s; //将新结点s插入尾结点*r之后
        r = s;       //r指向新的尾结点s
    }
    cout << endl;
}

// 单链表取值
bool GetElem(LinkList &L, int pos, int &ret)
{
    LinkList p;
    int j = 1;
    p = L->next;
    while (j<pos && p) //顺链域向后扫描,直到p指向第pos个元素或p为空
    {
        p = p->next;  //p指向下一个结点
        j++;          //计数器j相应加1
    }
    while (j>pos || !p)  // pos 值不合法 pos>n或 pos<1
    {
        return false;
    }
    ret = p->data;
    return true;
}
// 单链表查找元素
bool LocateElem(LinkList &L, int value, int &ret)
{
    LinkList p;
    p = L->next;
    int index = 1;
    while (p) // 顺链域向后扫描,直到p为空或p所指结点的数据域等于value
    {
        if (p->data == value)
        {
            ret = index;
            return true;
        }
        p = p->next;  // p指向下一个节点
        index++;
    }
    return false;
}

// 单链表插入值
bool ListInsert(LinkList &L, int pos, int value)
{
    LinkList p = L;
    LinkList s = new Lnode;
    int j = 0;
    while (p && j < pos - 1) // 查找第pos - 1个结点,p指向该结点
    {
        p = p->next;
        j++;
    }
    if (!p || j > pos - 1)  // pos > n+1 或者 pos < 1
    {
        return false;
    }
    s->data = value;
    s->next = p->next;
    p->next = s;
    return true;
}

// 单链表删除元素
bool ListDelete(LinkList &L, int pos)
{
    LinkList p;
    int j = 0;
    p = L;
    while ((p->next) && (j < pos - 1))
    {
        p = p->next;
        j++;
    }
    if (!(p->next) || (j > pos - 1))
    {
        return false;
    }
    p->next = p->next->next;
    return true;
}

// 输出单链表
void ListPrint(LinkList &L)
{
    LinkList p;
    p = L->next;
    while (p)
    {
        cout << p->data << "\t";
        p = p->next;
    }
    cout << endl;
}

int main()
{
    int pos, value, ret;
    cout << "1. 初始化" << endl;
    cout << "2. 创建单链表(前插法)" << endl;
    cout << "3. 创建单链表(尾插法)" << endl;
    cout << "4. 取值(按位置)" << endl;
    cout << "5. 查找(某个节点元素)" << endl;
    cout << "6. 插入" << endl;
    cout << "7. 删除" << endl;
    cout << "8. 输出" << endl;
    cout << "0. 退出" << endl;
    int choose = -1;
    LinkList L;
    while (choose)
    {
        cout << "请选择操作: ";
        cin >> choose;
        switch (choose)
        {
        case 1:
            cout << "初始化单链表" << endl;
            if (InitList(L))
            {
                cout << "单链表初始化成功" << endl;
            }
            else
            {
                cout << "单链表初始化失败" << endl;
            }
            break;
        case 2:
            cout << "头插法创建单链表" << endl;
            CreatListHead(L);
            ListPrint(L);
            break;
        case 3:
            cout << "尾插法创建单链表" << endl;
            CreatListFeet(L);
            ListPrint(L);
            break;
        case 4:
            cout << "单链表取值" << endl;
            cout << "请输入要取值的位置: ";
            cin >> pos;
            if (GetElem(L, pos, ret))
            {
                cout << "取值成功,取出来的值是: " << ret << endl;
            }
            else
            {
                cout << "取值失败, 不存在该位置的值" << endl;
            }
            break;
        case 5:
            cout << "请输入所要查找元素: ";
            cin >> value;
            if (LocateElem(L, value, ret))
            {
                cout << "查找成功, 在第 " << ret << " 个位置" << endl;
            }
            else
            {
                cout << "查找失败, 不存在: " << value << endl;
            }
                
            break;
        case 6:
            cout << "请输入插入的位置和元素(用空格隔开):";
            cin >> pos >> value;
            if (ListInsert(L, pos, value))
            {
                cout << "插入成功.\n\n";
            }
            else
            {
                cout << "插入失败!\n\n";
            }    
            ListPrint(L);
            break;
        case 7:
            cout << "请输入所要删除的元素位置i: ";
            cin >> pos;
            if (ListDelete(L, pos))
            {
                cout << "删除成功!\n";
            }
            else
            {
                cout << "删除失败!\n";
            }
            ListPrint(L);
            break;
        case 8:
            ListPrint(L);
            break;
        default:
            break;
        }
    }
    return EXIT_SUCCESS;
}

3.2 双向链表

3.2.1 双向链表的定义

/*
    单向链表有一个指针,只能向后操作,不可以向前操作。双向链表有两个指针,可以向前后两个方向操作。
*/
struct DuLnode
{
    int data;
    struct DuLnode *next, *prior;
}DuLonode, *DuLinkList;

3.2.2 双向链表的创建

/*
    双向链表也可以采用头插法和尾插法创建,一般采用头插法。
*/
S->next = L->next;
L->next->prior = S;
s->prior = L;
L->next = S;

3.2.3 双向链表的插入

/*
    单向链表要在第i个结点之前插入一个元素,则必须先找到第i-1个结点。双向链表直接找到第i个结点,就可以把新结点插入到第i个结点之前。
*/
p->prior->next = S;
S->prior = p->prior;
S->next = p;
p->prior = S;

3.2.4 双向链表的删除

/*
    删除一个结点,实际上是把这个结点跳过去。单向链表中,必须先找到第i-1个结点,才能把第i个结点跳过去。双向链表直接找到第i个结点,然后修改指针即可。
*/
p->prior->next = p->next;
p->next->prior-> p->prior;

 

 

3.2.5 双向链表的算法时间复杂度

 

 

3.2.6 双向链表的优缺点

优点:链表是动态存储,不需要预先分配最大空间。插入删除不需要移动元素。

缺点:每次动态分配一个结点,每个结点的地址是不连续的,需要有指针域记录下一个结点的地址,指针域需要占用一个int的空间,因此存储密度低(数据所占空间/结点所占总空间)。存取元素必须从头到尾按顺序查找,属于顺序存取。

3.2.7 双向链表的代码整合

 

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

typedef struct DuLNode
{
    int data;  // 数据域
    struct DuLNode *prior, *next;
}DuLNode, *DuLinkList;

// 双向链表初始化
bool InitDuList(DuLinkList &L)
{
    L = new DuLNode;
    if (L == NULL)
    {
        return false;
    }
    L->prior = NULL;
    L->next = NULL;
}

// 前插法创建双向链表
void CreateDuList(DuLinkList &L)
{
    int count;
    DuLinkList s;
    L = new DuLNode;
    L->prior = L->next = NULL;
    cout << "请输入元素个数: ";
    cin >> count;
    for (int i = 1; i <= count; i++)
    {
        s = new DuLNode;
        cout << "请输入第 " << i << " 个元素的值: ";
        cin >> s->data;
        if (L->next)
        {
            L->next->prior = s;
        }
        s->next = L->next;  // 新节点的下一个节点是原来头结点的下一个节点
        s->prior = L;  // 新节点的前驱结点是头结点
        L->next = s;   //将新结点s插入到头结点之后
    }
}

// 双向链表取值
bool GetElem(DuLinkList &L, int pos, int &ret)
{
    DuLinkList p;
    p = L->next;
    int j = 1;
    while (j < pos && p)
    {
        p = p->next;
        j++;
    }
    if (!p || j > pos)
    {
        return false;
    }
    ret = p->data;
    return true;
}

// 双向链表查找值
bool LocateElem(DuLinkList &L, int value, int &ret)
{
    DuLinkList p;
    p = L->next;
    int j = 1;
    while (p)
    {
        if (p->data == value)
        {
            ret = j;
            return true;
        }
        j++;
        p = p->next;
    }
    return false;
}

// 双向链表插入值
bool ListInsert(DuLinkList &L, int pos, int value)
{
    DuLinkList p = L;
    DuLinkList s = new DuLNode;
    int j = 0;
    while (p && j < pos)
    {
        p = p->next;
        j++;
    }
    if (!p || j > pos)
    {
        return false;
    }
    s->data = value;
    p->prior->next = s;
    s->prior = p->prior;
    s->next = p;
    p->prior = s;
    return true;
    

}

// 双线链表删除
bool ListDelete(DuLinkList &L, int pos, int &ret)
{
    DuLinkList p = L;
    int j = 0;
    while (p && j < pos)
    {
        p = p->next;
        j++;
    }
    if (!p || j > pos)
    {
        return false;
    }
    if (p->next)  // 如果 p 的直接后继节点存在的话
    {
        p->next->prior = p->prior;
    }
    p->prior->next = p->next;
    ret = p->data;
    delete p;
    return true;
}


// 输出双向链表
void Listprint(DuLinkList &L)
{
    DuLinkList p;
    p = L->next;
    while (p)
    {
        cout << p->data << "\t";
        p = p->next;
    }
    cout << endl;
}


int main()
{
    int pos, value, ret;
    int choose = -1;
    DuLinkList L;
    while (choose)
    {
        cout << "1. 初始化\n";
        cout << "2. 创建双向链表(头插法)\n";
        cout << "3. 取值\n";
        cout << "4. 查找\n";
        cout << "5. 插入\n";
        cout << "6. 删除\n";
        cout << "7. 输出\n";
        cout << "0. 退出\n";
        cout << "请输入数字选择:";
        cin >> choose;
        switch (choose)
        {
        case 1:
            cout << "双向链表初始化" << endl;
            if (InitDuList(L))
                cout << "初始化一个空的双向链表!\n";
            else
            {
                cout << "初始化失败" << endl;
            }
            break;
        case 2:
            cout << "前插法创建双项链表" << endl;
            CreateDuList(L);
            cout << "前插法创建双向链表输出结果:\n";
            Listprint(L);
            break;
        case 3:
            cout << "请输入一个位置用来取值: ";
            cin >> pos;
            if (GetElem(L, pos, ret))
            {
                cout << "查找成功" << endl;
                cout << "" << pos << " 个元素是:" << ret << endl;
            }
            else
            {
                cout << "查找失败,不存在位置: " << pos << endl;
            }
            break;
        case 4:
            cout << "请输入所要查找元素: ";
            cin >> value;
            if (LocateElem(L, value, ret))
            {
                cout << "查找成功, 在第 " << ret << " 个位置" << endl;
            }
            else
            {
                cout << "查找失败,不存在这个元素 " << endl;
            }
            break;
        case 5:
            cout << "双向链表位置之前插入值,请输入插入的位置和元素(用空格隔开): ";
            cin >> pos >> value;
            if (ListInsert(L, pos, value))
            {
                cout << "插入成功!" << endl;
            }
            else
            {
                cout << "插入失败!" << endl;
            }
            Listprint(L);
            break;
        case 6:
            cout << "请输入所要删除的元素位置: ";
            cin >> pos;
            if (ListDelete(L, pos, ret))
            {
                cout << "删除成功, 删除的元素是: " << ret << endl;
            }
            else
            {
                cout << "删除失败, 不存在该位置" << endl;
            }
            Listprint(L);
            break;
        case 7:
            Listprint(L);
            break;
        default:
            break;
        }
    }
    return EXIT_SUCCESS;
}

四、循环链表

循环链表是指最后一个节点指向的不是空,而是头节点。

 

五、快慢指针

带有头结点的单链表L,设计一个高效算法求L中的中间结点。

解题思路:使用快慢指针来解决。一个快指针,一个慢指针,快指针走两步慢指针走一步,当快指针指向结尾的时候,慢指针刚好指向中间结点。

 

 

LinkList findmiddle(LinkList L)
{
    LinkList p, q;
    p = L;  // p为快指针
    q = L;  // q为慢指针
    while(p!=NULL && p->next!=NULL)
    {
        p = p->next->next;  // 快指针走两步
        q = q->next;        // 慢指针走一步
    }
    return q;  // 返回中间节点的指针
}

 

思考:如何在单链表中查找倒数第k个结点?
仍然可以使用快慢指针,慢指针不要动,快指针先走k-1步,然后两个指针一起同样的速度走,当快指针走到终点时,慢指针正好停留在倒数第k个结点。为什么呢?
因为它们之间的距离始终保持k-1。

 

posted on 2022-04-06 18:54  软饭攻城狮  阅读(334)  评论(0编辑  收藏  举报

导航