数据结构学习笔记——线性表

线性表的顺序表示(顺序表)

特点:

1.随机访问:即可以在O(1)时间内找到第i个元素
2.存储密度高,每个节点只存储数据元素
3.扩展容量不方便(即使采用动态分配,扩展长度的时间复杂度也比较高)
4.插入、删除操作不方便,需要移动大量元素

顺序表的静态分配

#define MAXsize 100 //静态顺序表的最大容量
typedef struct//静态顺序表结构
{
	int data[MAXsize];
	int length;
}Sqlist;

静态顺序表初始化

void Static_Initlist(Sqlist& L)//初始化静态顺序表
{
	L.length = 0;
	for (int i = 0; i < MAXsize; i++)
	{
		L.data[i] = 0;
	}
}

顺序表的动态分配

typedef struct//动态顺序表结构
{
	int* data;//指示动态分配数组的指针
	int length, Maxsize;
}Seqlist;
  • 动态顺序表初始化
#define Initsize 100//动态初始容量
void Dynamic_Initlist(Seqlist& L)//初始化动态顺序表
{
	L.data = new int[Initsize];//申请堆
	L.length = 0;
	L.Maxsize = Initsize;
} 
  • 动态顺序表的动态增加
void IncreaseSize(Seqlist& L, int len)//增加动态顺序表的长度
{
	int* p = L.data;
	L.data = new int[L.Maxsize + len];//申请一快足够大的堆内寸
	for (int i = 0; i < L.length; i++)//将原堆内存中的数据赋值到新内存的对应位置
	{
		L.data[i] = p[i];
	}
	L.Maxsize += len;//顺序表的最大长度+len
	delete p;//释放原堆内存
}

顺序表的基本操作

插入操作

bool ListInsert(Sqlist& L, int i, int e)//插入操作 i:位置 e:插入元素
{
	if (i<1 || i>L.length + 1)return 0;//判断i的范围是否有效
	if (i > MAXsize)return 0;//当前空间以满不能插入
	for (int j = L.length; j >= i; j--)//将i个元素及之后的元素后移一位
	{
		L.data[j] = L.data[j - 1];
	}
	L.data[i - 1] = e;//将元素e插入到位置i
	L.length++;//L的长度增加1
	return 1;
}
  • 最好情况:在表尾插入(i=n+1)元素后移指令不执行,时间复杂度为O(1)
  • 最坏情况:在表头插入(i=1)元素后移执行n次,时间复杂度为O(n)
  • 平均情况:假设新元素插入到任何一个位置的概率都是相同的,即i=1,2,3…,length+1的概率都是P=1/(n+1),节点平均移动的次数为=nP+(n-1)P+(n-2)P+…+1P=n(n+1)/2 * 1/(n+1) =n/2次,时间复杂度为O(n)

删除操作

bool ListDelete(Sqlist& L, int i, int& e)//删除操作 i删除元数的位置 e接收被删除元素的值的变量
{
	if (i<1 || i>L.length)return 0;//判断i的范围是否有效
	e = L.data[i - 1];//被删除元素赋值给e
	for (int j = i; j < L.length; j++)
	{
		L.data[j - 1] = L.data[j];//将第i个位置后的元素前移
	}
	L.length--;//线性表长度减一
	return 1;
}
  • 最好情况:删除表尾元素,不需要移动其他元素(i=n),时间复杂度为O(1)
  • 最坏情况:删除表头元素(i=1),需要移动除表头外的所有元素,时间复杂度为O(n)
  • 平均情况:假设删除一个元素的概率相同,即i=1,2,3…,length的概率为P=1/n,平均循环次数=(n-1)P+(n-2)P+…1P=n(n-1)/2 * 1/n=(n-1)/2,时间复杂度为O(n)

查找操作

int LocateInt(Sqlist L, int e)//按值查找(顺序查找) e:查找的元素
{
	int i;
	for (i = 0; i < L.length; i++)//遍历查找
	{
		if (L.data[i] == 3)return i + 1;
	}
	return -1;//查找失败返回-1
}
  • 最好情况:查找元素就在表头,仅需比较一次,时间复杂度O(1)
  • 最坏情况:查找元素在表尾或不存在,需要比较n次时间复杂度O(n)
  • 平均情况:假设查找一个元素的概率相同,即i=1,2,3…,length的概率为P=1/n,平均循环次数=nP+(n-1)P+(n-2)P+…1P=n(n+1)/2 * 1/n=(n+1)/2,时间复杂度为O(n)

线性表的链式表示(链表)

单向链表

定义一个链表

typedef struct {//单链表结构体
    int data;
    LNode* next;
}LNode,*LinkList;//  LinkList 为指向LNode类型的指针 即 LinkList替换为LNode * 类型

链表初始化

  • 带头节点初始化
bool InitList(LinkList& L)
{
    L = new LNode;
    if (L == NULL)
        return 0;//分配失败
    L->next = NULL;
    return 1;
}
  • 不带头节点初始化
bool InitList(LinkList& L)//不带头节点链表初始化
{
    L = NULL; //空表 防止遗留脏数据
    return 1;
}

链表的基本操作

  • 带头节点插入操作
bool ListInsert(LinkList& L, int i, int e)//在带头节点的链表中第i个位置插入元素e
{
    if (i < 1)return 0;
    LNode* p;//创建指针p指向当前头节点
    p = L;
    int j = 0;
    while (p != NULL && j < i - 1)//循环找到第i-1个节点
    {
        p = p->next;
        j++;
    }
    if (p == NULL)return 0;//检测链表中是否有第i个节点
    LNode* s = new LNode;//创建一个节点、
    s->data = e;
    s->next = p->next;//将新节点插入p(i-1)节点与第i个节点之间
    p->next = s;
    return true;
} 

平均时间复杂度O(n)

  • 不带头节点的插入操作
bool ListInsert(LinkList& L, int i, int e)//不带头节点的链表中第i个位置插入元素e
{
    if (i < 1)return 0;
    if (i == 1)
    {
        LNode* s = new LNode;
        s->data = e;
        s->next = L;
        L = s;//头指针指向第一个节点
    }
    LNode* p;//创建指针p指向当前第一个节点
    p = L;
    int j = 1;
    while (p != NULL && j < i - 1)//循环找到第i个节点
    {
        p = p->next;
        j++;
    }
    if (p == NULL)return 0;//检测链表中是否有第i个节点
    LNode* s = new LNode;//创建一个节点、
    s->data = e;
    s->next = p->next;//将新节点插入p(i-1)节点与第i个节点之间
    p->next = s;
    return true;
}

平均时间复杂度O(n)

  • 给定节点后插
bool InsertNextNode(LNode* p, int e)//在指定节点后插入节点
{
    if (p == NULL)return 0;
    LNode* s = new LNode;
    if (s == NULL)return 0;//申请内存失败
    s->data = e;
    s->next = p->next;
    p->next = s;
    return 1;
}

时间复杂度O(1)

  • 给定节点前插

插入到给定节点后面,然后交换新节点与给定节点的值

bool InsertFrontNode(LNode* p, int e)//在指定节点前插入节点
{
    if (p == NULL)return 0;
    LNode* s = new LNode;
    if (s == NULL)return 0;//内存分配失败
    s->next = p->next;//将新节点插入指定节后然后交换p节点和新节点的data
    p->next = s;
    s->data = p->data;
    p->data = e;
    return 0;
}

时间复杂度O(1)

  • 按位序删除节点(带头节点)
bool ListDelete(LinkList& L, int i, int& e)//按为序删除节点(带头节点)
{
    if (i < 1)return 0;
    LNode* p;//指针p指向当前节点
    p = L;
    int j = 0;
    while (p != NULL && j < i - 1)//循环遍历找到第i-1个节点
    {
        p = p->next;
        j++;
    }
    if (p == NULL)return 0;//i值不合法
    if (p->next = NULL)return 0;//第i-1个节点之后在无节点
    LNode* q = p->next;//新建指针指向p的后驱节点
    e = q->data;//获取要删除的第i个节点的值
    p->next = q->next;//将q节点从链表中断开
    delete q;
    return 1;
}

最坏、平均时间复杂度O(n)
最快时间复杂度O(1)

  • 删除指定节点

交换删除节点p与后继节点的数据,然后删除p的后继节点

bool DeleteNode(LNode *p)//删除指定节点
{
    if (p == NULL)return 0;
    LNode* q = p->next;//创建指针指向p后继的节点
    p->data = p->next->data;//p和后继节点交换数据
    p->next = q->next;//将q节点从中断开
    delete q;
    return 1;
}

时间复杂度O(1)

  • 按位查找
LNode* GetElem(LinkList L,int i)//按位查找(带头节点,头节点位第0位) 返回第i个节点的指针
{
    if (i < 0)return NULL;
    LNode* p; //指针p指向当前节点
    p= L;
    int j = 0;
    while (p != NULL && j < i)//循环找到第i个节点 并p指向
    {
        p = p->next;
        j++;
    }
    return p;
}

平均时间复杂度O(n)

  • 按值查找
LNode* LocatedElem(LinkList L, int e)//按值查找 返回值为e的节点指针
{
    LNode* p = L->next;
    while (p != NULL && p->data != e)
    {
        p = p->next;
    }
    return p;//找到返回节点指针 ,没找到返回NULL
}

平均时间复杂度O(n)

  • 统计表的长度
int Length(LinkList L)//统计表的长度
{
    int len = 0;
    LNode* p = L;
    while (p->next != NULL)
    {
        len++;
        p = p->next;
    }
    return len;
}

时间复杂度O(n)

  • 链表的建立

  • 尾插法建立链表

LinkList List_TailInsert(LinkList& L)//尾插法建立链表
{
    int x;
    L = new LNode;//建立头节点
    LNode* s, * r = L;//r为表尾指针
    cin >> x;//输入链表当前节点的值
    while (x != 9999)//输入9999停止
    {
        s = new LNode;
        s->data = x;
        r->next = s;
        r = s;//r指向新的表尾节点
        cin >> x;
    }
    r->next = NULL;//尾节点置空
    return L;
}

时间复杂度O(n)

  • 头插法建立链表
LinkList List_HeadInsert(LinkList& L)//头插法逆向建立链表
{
    LNode* s;
    int x;
    L = new LNode;//创建头节点 
    L->next = NULL;//初始为空链表
    cin >> x;//输入节点的值
    while (x != 9999)
    {
        s = new LNode;
        s->data = x;
        s->next = L->next;//将新节点插入表中,L为头指针
        L->next = s;
        cin >> x;
    }
    return L;
}

时间复杂度O(n)

双向链表

定义一个双向链表

typedef struct {
    int data;
    DNode* prior, * next;
}DNode,*DlinkList;// 双链表结构体

双向链表初始化

bool InitDLinkList(DlinkList & L)//双向链表初始化 
{
    L = new DNode;
    if (L == NULL)return 0;//分配内存失败返回0
    L->prior = NULL;//前指针值空
    L->next = NULL;//后指针值空
    return 1;
}

双向链表的基本操作

双链表的插入
  • 在给定节点之后插入节点
bool InsertNextDNode(DNode* p, DNode* s)//在p节点之后插入s节点
{
    s->next = p->next;
    p->next->prior = s;
    s->prior = p;
    p->next = s;
}

时间复杂度O(1)

双链表的删除
  • 删除指定节点的后继节点
bool DeleteNextDNode(DNode* p)//删除p节点的后继节点
{
    if (p == NULL)return 0;
    DNode* q = p->next;//找到p的后继节点
    if (q == NULL)return 0;//p没有后继节点
    p->next = q->next;
    if (q->next != NULL)q->next->prior = p;//q节点不是最后一个节点
    delete q;
    return 1;
}

时间复杂度O(1)

循环单链表

定义一个循环链表

typedef struct {
    int data;
    SNode* next;
}SNode,*SLinkList;//循环链表结构体

初始化一个循环链表

bool InitSLinkList(SLinkList& L)//初始化一个循环单链表
{
    L = new SNode;
    if (L == NULL)return 0;
    L->next = L;//头节点next指向头节点
    return 1;
}

判断节点是否为头节点

bool isTail(SLinkList L, SNode* p)//判断节点p是否为头节点
{
    if (p->next == L)return 1;
    else return 0;
}

循环双链表

定义一个循环双链表

typedef struct {
    int data;
    DSNode* prior, * next;
}DSNode,*DSLinkList;//循环双链表结构体

初始化一个双向循环链表

bool InitDSLinkList(DSLinkList& L)//初始化一个双向循环链表
{
    L = new DSNode;
    if (L == NULL)return 0;
    L->prior = L;//头节点的前驱指针指向头节点
    L->next = L;//头节点的后驱指针指向头节点
    return 1;
}

双向循环链表 在指定节点之后插入一个节点

bool InsertNextDSNode(DSNode* p, DSNode* s)//双向循环链表 p节点之后插入s节点
{
    s->next = p->next;
    p->next->prior = s;
    s->prior = p;
    p->next = s;
}

双向循环链表删除指定节点的后继节点

bool DeleteSDNode(DSNode* p)//双向循环链表中 删除指定节点的后继节点
{
    if (p == NULL)return 0;
    DSNode* q = new DSNode;
    q = p->next;
    p->next = q->next;
    q->next->prior = p;
    delete q;
}

静态链表

静态链表:分配一整片连续的内存空间,各个节点集中安置,其中节点存放数据域data和指针域next,与之前不同的是这里的指针是系欸但的相对地址(即数组的下标)又称游标。

  • 优点:增删操作不需要大量移动元素
  • 缺点:不能随机存取,只能从头结点开始依次往后查找;容量固定不可变

适用场景:1.不支持指针的低级语言 2.数据元素数量固定不变的场景(如操作系统的文件分配表FAT)

定义一个静态链表

typedef struct {
    int data;
    int next;
}StaticLinkList[MAXsize];//静态链表结构体

查找:从头节点出发挨个往后遍历结点,时间复杂度O(n)
插入为序为i的结点:

1.找到一个空的结点存入数据元素
2.从头结点出发找到为序为i-1的结点
3.修改新结点的next
4。修改i-1结点的next

如何判定结点为空?
初始化时将所有空的结点之指针赋值为某一负值,如-2

删除某个结点:
1.从头结点出发找到前驱结点
2.修改前驱结点的游标
3.被删除结点next设为-2

顺序表与链表的比较

相同:都属于线性结构,都是线性结构

顺序表:

优点:支持随机存取、存取密度高
缺点:大片连续空间分配不方便,改变容量不方便
初始化:需要分配大片的连续空间,如分配空间过小则之后不方便扩展容量;若分配空间过大,则浪费资源
静态分配(静态数组):容量不可变
动态分配(动态数组):容量可变,但需要移动大量元素,时间代价高
操作时间复杂度
插入O(n)(主要移动元素)
按位查找O(1)
按值查找O(n)(若元素有序则O(log2n)(顺序表效率一般比链表高)

链表:

优点:离线的小空间分配方便,改变容量方便
缺点:不可随机存取,存储密度低
初始化:只需分配一个头结点(也可以不要头结点,只声明一个头指针),之后方便扩展
操作时间复杂度
插入O(n)(主要查找i目标元素)(链表效率一般比顺序表更高)
按位查找O(n)
按值查找O(n)

数据结构学习笔记——线性表_第1张图片
表长难以估计、经常要增加/删除元素 ——链表
表长可估计、查询(搜索)操作较多——顺序表

你可能感兴趣的:(笔记,学习,数据结构)