数据结构丨线性表

知识点总结

线性结构

线性结构:在数据元素的非空有限集中

1、存在唯一的一个被称为“第一个”的数据元素;

2、存在唯一的一个被称为“最后一个”的数据元素;

3、除第一个之外,集合中的每个数据元素均只有一个前驱;

4、除最后一个外,集合中的每个数据元素均只有一个后继。

线性结构反映节点间的逻辑结构是一对一的。

线性结构包括线性表、堆栈、队列、字符串、数组


线性表

线性表是n个数据元素的有限序列(a_1,a_2,...,a_n)

同一线性表中的元素必须具有相同的特性

线性表中元素的个数n定义为线性表的长度,n=0时成为空表

线性表的顺序表示与实现

线性表的顺序表示:指用一组地址连续的存储单元依次存储线性表的数据元素

顺序存储结构:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构,有这种存储结构的线性表称为顺序表

顺序表:以元素在计算机内“物理位置相邻”来表示线性表中数据元素之间的逻辑关系

顺序存储结构的优缺点

优点:

  • 逻辑相邻,则物理相邻
  • 可随机存取任一元素(可以直接寻址,而链表只能通过next指针)
  • 存储空间使用紧凑

缺点: 

  • 插入、删除操作需要移动大量的元素
  • 预先分配空间需按最大空间分配,利用不充分

线性表的操作实现

数据结构的基本运算包括修改、插入、删除、查找、排序。

线性表的修改只需要一步:

a[i]=b;

将b的值赋予给a[i]。

线性表的插入:

指在线性表的第i个元素之前插入一个新的数据元素。

实现步骤:

1、将第n至第i个元素i向后移动一个位置(共n-1-i个元素);

2、将第i个元素赋值;

3、表长n=n+1;

时间复杂度:

假设p_i是在第i个元素之前插入一个元素的概率,则在长度为n的线性表中插入一个数据元素时所需移动元素次数的期望值为

E_{is}=\sum_{i=1}^{n+1}{p_i(n-i-1)}

假定在线性表的任何位置上插入元素时等概率的,即p_1=\frac{1}{n+1},则

E_{is}=\frac{1}{n+1}\sum_{i=1}^{n+1}{(n-i-1)}=\frac{n}{2}

故线性表的插入操作的时间复杂度为O(n);空间复杂度为O(1),因为没有占用辅助空间。

线性表的删除:

指删除线性表的第i个数据元素。

实现步骤:

1、将从第i+1至第n个元素(共n-i个元素)依次向前移动一个位置

2、表长减1

时间复杂度:

计算方法同插入算法所用的方法,E_{dl}=\sum_{i=1}^{n}q_i(n-i)=\frac{1}{n}\sum_{i=1}^{n}(n-i)=\frac{n-1}{2}

综上,线性表的插入、删除操作的时间复杂度均为O(n),空间复杂度为O(1)。


线性表的链式表示与实现

线性链表特点:

1、用一组任意的存储单元存储线性表的数据元素

2、利用指针实现了用不相邻的存储单元存放逻辑上相邻的元素,因此也无法随机存取

3、每个数据元素,除存储本身信息外,还需存储其直接后继的信息节点


基础知识题

1、描述以下三个概念的区别:

头指针:指指向链表第一个结点的指针。

头结点:为操作方便,常在链表的首元结点之前附设一个结点,称为头结点。其作用是为了对链表操作时,可以对空表、非空表的情况以及对首元结点进行统一处理。

首元结点:指链表中存储线性表第一个数据元素的结点。

2、填空题

(1)在顺序表中插入或删除一个元素,需要平均移动表中一半元素,具体移动的元素个数与表长和该元素在表中的位置有关。

(2)在单链表中,除了首元结点外,任一结点的存储位置由其直接前驱结点的链域的值决定。

(3)在单链表中设置头结点的作用是插入和删除首元素时不必进行特殊处理。

3、在什么情况下用顺序表比链表好? 

 在需要频繁随机访问元素的情况下。


第二周上机作业

1、编写算法对一顺序表进行就地逆置

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int value, i, temp, length=0;
	int *a;
	FILE* fp = fopen("D:\\Cfiles\\sequenceList.txt", "r");
	if (fp == NULL)
	{
		printf("error opening the file");
		return 1;
	}

	else
	{
		while (fscanf(fp, "%d", &value) == 1)
		{
			length++;
		}
		rewind(fp);//重置文件指针到开头
		a = (int*)malloc(length * sizeof(int));
		for (i = 0; i < length; i++)
		{
			fscanf(fp, "%d", &a[i]);
		}
		fclose(fp);

		printf("原数组的存放顺序为:");
		for (i = 0; i < length; i++)
		{
			printf("%d  ", a[i]);
		}
		printf("\n");

		for (i = 0; i < 1+(length - 1)/2; i++)
		{
			temp = a[i];
			a[i] = a[length - 1 - i];
			a[length - 1 - i] = temp;
		}

		printf("顺序表逆序后的结果为:");
		for (i = 0; i < length; i++)
		{
			printf("%d  ", a[i]);
		}
		free(a);

	}

	return 0;
}

2、编写一算法对一单链表进行就地逆置

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>

typedef struct node
{
	int value;
	struct node* next;
} Node;

Node* reverseNode(Node* head, int number);
void printLinkedList(Node* head);

int main()
{
	int num;
	Node* head = NULL;
	FILE* fp = fopen("D:\\Cfiles\\LinkedList.txt", "r");
	if (fp == NULL)
	{
		printf("error opening the file");
		return 1;
	}

	printf("单链表的原始顺序为:");
	while (fscanf(fp, "%d", &num) == 1)
	{
		printf("%d ",num);
		head=reverseNode(head, num);
	}
	fclose(fp);
	printf("\n倒置后的顺序为:");
	printLinkedList(head);

	return 0;
}

Node* reverseNode(Node* head, int number)
{
	Node* p = (Node*)malloc(sizeof(Node));
	p->value = number;
	p->next = head;
	head = p;
	return head;
}

void printLinkedList(Node* head)
{
	while (head != NULL)
	{
		printf("%d ", head->value);
		head = head->next;
	}
}

方法二(原作者@happy egg,熊猫只是搬运工~)

首先创建链表将输入的数字存储在一个链表中,然后创建sort函数,用函数实现链表的逆置

Node* sort(Node* head)

{

     Node* next = (Node*)malloc(sizeof(Node));

     Node* pre = (Node*)malloc(sizeof(Node));

     Node* current = head;

     while(current != NULL)

     {

            next = current->next;

            current->next=pre;

            pre = current;

            current = next;

     }

     head = pre;

     return head;

3、对两个递增的单链表进行二路归并

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>

typedef struct node
{
	int value1;
	int value2;
	struct node* next;
} Node;

Node* addToLink1(Node* head, int a, int b);
Node* addToLink2(Node* head, int a, int b);
void printLinkedList(Node* head);

int main()
{
	FILE* fp1, * fp2;
	int a, b;
	Node* head = NULL;
	fp1 = fopen("D:\\Cfiles\\source1.txt", "r");
	if (fp1 == NULL)
	{
		printf("error opening the file");
		return 1;
	}
	while (fscanf(fp1, "%d,%d", &a, &b)==2)
	{
		head = addToLink1(head, a, b);
	}
	fclose (fp1);

	fp2 = fopen("D:\\Cfiles\\source2.txt", "r");
	if (fp2 == NULL)
	{
		printf("error opening the file");
		return 1;
	}
	while (fscanf(fp2, "%d,%d", &a, &b)==2)
	{
		head = addToLink2(head, a, b);
	}
	fclose(fp2);

	printLinkedList(head);
	return 0;
}

Node* addToLink1(Node* head, int a, int b)
{
	Node* p = (Node*)malloc(sizeof(Node));
	Node* current = head;
	p->value1 = a;
	p->value2 = b;
	p->next = NULL;

	if (head == NULL)
	{
		head = p;
	}

	else
	{
		while (current->next != NULL)
		{
			current = current->next;
		}
		current->next = p;
	}
	return head;
}

Node* addToLink2(Node* head, int a, int b)
{
	Node* p = (Node*)malloc(sizeof(Node));
	Node* current = (Node*)malloc(sizeof(Node));
	p->value1 = a;
	p->value2 = b;
	p->next = NULL;

	if (b < head->value2)
	{
		p->next = head;
		head = p;
	}
	current = head;

	while (current->next != NULL && b >= current->next->value2)
	{
		current = current->next;
	}

	if (b == current->value2)
	{
		current->value1 += a;
	}
	else
	{
		p->next = current->next;
		current->next = p;
	}

	return head;
}

void printLinkedList(Node* head)
{
	printf("两个单链表归并之后的结果为:\n");
	while (head != NULL)
	{
		printf("%d,%d\n", head->value1, head->value2);
		head = head->next;
	}
}

上机总结

  • while(fscanf(fp2, "%d,%d", &a, &b)==2):函数fscanf的返回值是成功读取和匹配的参数个数,如果成功读取了两个整数,则返回值为2。因此,可以通过检查fscanf的返回值来判断是否成功读取了两个整数。
  • 对两个递增的单链表进行二路归并时,addToLink1()函数相当于创建一个链表;addToLink2()函数通过判断多项式中每一项的幂次与当前节点的大小,决定插入或合并。
  • 需要注意的是,第二个文件读取的节点有可能会变成原链表的头节点,还需要对该节点与原头节点所指向的幂次进行比较,若该节点幂次更小,将成为新的节点。
  • while (current->next != NULL && b >= current->next->value2)这句代码写了很久,一定要注意是把b和current->next->value2比较而不是current->value2,这样才能将新节点插入到current节点之后。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值