zoukankan      html  css  js  c++  java
  • 第一次博客作业--线性表

    0.PTA得分截图#

    1.本周学习总结(0-4分)#

    1.1 总结线性表内容##

    • 顺序表结构体定义
    typedef struct
    {
    	ElemType data[MaxSize];		//存放顺序表元素
    	int length;			//存放顺序表的长度
    } List;
    typedef List* SqList;
    
    • 顺序表插入和删除
    void InsertSq(SqList& L, int x)//顺序表插入
    {
    	int i, j;
    	for (i = 0; i < L->length; i++)
    	{
    		if (L->data[i] > x)
    		{
    			for (j = L->length; j > i; j--)
    			{
    				L->data[j] = L->data[j - 1];
    			}
    			L->data[i] = x;
    			L->length++;
    			break;
    		}
    	}
    	if (i == L->length)
    	{
    		L->data[i] = x;
    		L->length++;
    	}
    }
    void DelSameNode(List& L)//顺序表删除相同元素
    {
    	int i, j, k;
    	for (i = 0; i < L->length; i++)
    	{
    		j = i + 1;
    		for (; j <= L->length; j++)
    		{
    			if (L->data[i] == L->data[j])
    			{
    				for (k = j; k < L->length - 1; k++)
    				{
    					L->data[k] = L->data[k + 1];
    				}
    				L->length--;
    			}
    			
    		}
    	}
    }
    
    • 链表结构体定义
    typedef struct LNode  		//定义单链表结点类型
    {
    	ElemType data;
    	struct LNode* next;		//指向后继结点
    } LNode, * LinkList;
    
    • 链表头插法、尾插法、有序链表插入、删除
    void CreateListF(LinkList& L, int n)//头插法建链表
    {
    	LinkList newptr;
    	LinkList oldptr;
    	LinkList nowptr;
    	int i;
    	L = new LNode;
    	L->next = NULL;
    	oldptr = L;
    	newptr = L->next;
    
    	for (i = 1; i <= n; i++)
    	{
    		nowptr = new LNode;
    		cin >> nowptr->data;
    		nowptr->next = oldptr->next;
    		oldptr->next = nowptr;
    		newptr = nowptr;
    	}
    
    }
    void CreateListR(LinkList& L, int n)//尾插法建带头结点链表
    {
    	LinkList node, tail;
    	int i;
    	L = new LNode;
    	L->next = NULL;
    	tail = L;
    	for (i = 1; i <= n; i++)
    	{
    		node = new LNode;
    		cin >> node->data;
    		tail->next = node;
    		tail = node;
    	}
    	tail->next = NULL;
    }
    void ListInsert(LinkList& L, ElemType e)//链表插入元素
    {
    	LinkList pre, ptr;
    	LinkList p;
    	p = new LNode;
    	p->data = e;
    	pre = L->next;
    
    	while (pre->next)
    	{
    		if (pre->next->data > e)
    		{
    			p->next = pre->next;
    			pre->next = p;
    			break;
    		}
    		else
    		{
    			pre = pre->next;
    		}
    	}
    	if (pre->next == NULL)
    	{
    		p->next = pre->next;
    		pre->next = p;
    	}
    }
    void ListDelete(LinkList& L, ElemType e)//链表删除元素
    {
    	LinkList pre, ptr;
    	pre = L;
    	int flag = 1;
    	if (L->next == NULL)
    	{
    		return;
    	}
    	while (pre->next)
    	{
    		if (pre->next->data == e)
    		{
    			ptr = pre->next;
    			pre->next = pre->next->next;
    			delete ptr;
    			flag = 0;
    			break;
    		}
    		else
    		{
    			pre = pre->next;
    		}
    	}
            if (flag==1)
    	{
    		cout << e << "找不到!" << endl;
    	}
    }
    
    • 有序表合并
    void MergeList(LinkList& L1, LinkList L2)//合并链表
    {
    	LinkList p1, p2, p3 = L1;
    	p1 = L1->next;
    	p2 = L2->next;
    	L1->next = NULL;
    
    	while (p1 && p2)
    	{
    		if (p1->data < p2->data)
    		{
    			p3->next = p1;
    			p3 = p1;
    			p1 = p1->next;
    		}
    		else if (p1->data > p2->data)
    		{
    			p3->next = p2;
    			p3 = p2;
    			p2 = p2->next;
    		}
    		else
    		{
    			p3->next = p1;
    			p3 = p1;
    			p1 = p1->next;
    			p2 = p2->next;
    		}
    	}
    	if (p1 != NULL)
    	{
    		p3->next = p1;
    	}
    	if (p2 != NULL)
    	{
    		p3->next = p2;
    	}
    }
    
    • 循环链表、双链表结构特点
      • 循环链表是单链表的变形,循环链表最后一个结点的指针不为NULL,而是指向了表的前端,为简化操作,在循环链表中往往加入表头结点,循环链表的特点是:只要知道表中某一结点的地址,就可搜寻到所有其他结点的地址。
      • 双链表有两个特点:一是可以从两个方向搜索某个结点,这使得链表的某些操作(如插入和删除)变得比较简单; 二是无论利用前链还是后链都可以遍历整个双向链表。

    1.2.谈谈你对线性表的认识及学习体会。##

    • 线性表有两种方式:
      1.顺序线性表 (也就是用数组实现的,在内存中有顺序排列,通过改变数组大小实现)
      2.链表 (不是用顺序实现的,用指针实现,在内存中不连续!)
      顺序线性表的缺点:1.插入和删除操作需要移动大量的元素。在顺序表上插入和删除平均要移动一半的元素。2.表的容量难以确定。因为数组的长度要提前声明。3.数组要求占用连续的存储空间,即使存储单元数超过所需要的数目,如果不连续也不能用。而链式存储就没有这种缺点,每一个结点都是程序员自己向计算机申请的,如果不需要的话就会释放掉,将内存返还给系统,而且表的容量是确定的,各个节点的存储地址是不连续的,各个节点通过自己的数据域存储信息,通过指针域来存储下一个结点的信息。但如果一个结点的指针域数据丢失了,那他的下一个结点就找不到了。
         单链表的构建有头插和尾插两种方法,无论是哪一种方法都需要从空表的构建开始。头插法就是新插入的每一个结点都在头节点之后,最先插入的节点变为尾节点,而尾插法是每个插入的结点都是插在链表的最后,作为尾节点出现的。这里代码就不给出。课本上都有,在链表构建好之后就可以进行一系列的操作。比如查找删除插入遍历等等。。。
      如果希望除了可以确定一个结点的后继外,还可以找到它的前驱,那么我们就可以构建双链表,就是在单链表的每个结点中在设置一个指向其前驱结点的指针域。和单链表类似,它一般也是由头指针唯一确定。
      对于单链表,每个结点只存储了向后的指针,到了尾结点就停止了向后的操作。我们将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。 循环链表和单链表的主要差异在于循环的判断条件上,之前是判断temp->next是否为空,现在是temp->next不等于头结点,.循环链表最后一个结点的 link 指针不 为NULL,而是指向了表的前端。 它只要知道表中某一结点的地址,就可搜寻到所有其他结点的地址。

    2.PTA实验作业(0-2分)#

    2.1.题目1:6-10 jmu-ds-有序链表的插入删除##

    2.1.1代码截图###



    2.1.2本题PTA提交列表说明。###

    Q:全部删除出现错误
    A:增加了一个链表为空时的附加条件,让其及时返回
    Q:增加的条件出现错误
    A:把=改成==就对了
    

    **2.2.题目2:6-11 jmu-ds-链表分割 **##

    2.2.1代码截图###


    2.2.2本题PTA提交列表说明。###

    Q:分割得到的第二条链表输出不完整
    A:修改了循环结束的条件,晚一点结束循环
    Q:链表长度为偶数时输出正确,为奇数时输出错误
    A:为偶数和奇数分别设置不同的条件
    

    **2.3.题目1:6-8 jmu-ds-链表倒数第m个数 **##

    2.3.1代码截图###

    2.3.2本题PTA提交列表说明。###

    Q:没有考虑无效位置
    A:增加了一个条件判断无效位置
    Q:出现了段错误,少考虑一个特殊的无效位置
    A:把m=0这个特殊的无效位置考虑进去就对了
    

    3.阅读代码(0--4分)#

    3.1 题目及解题代码##

    对链表进行插入排序

    插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
    每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。
    插入排序算法:
    插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
    每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
    重复直到所有输入数据插入完为止。
    示例 1:
    输入: 4->2->1->3
    输出: 1->2->3->4
    示例 2:
    输入: -1->5->3->4->0
    输出: -1->0->3->4->5

    ListNode* insertionSortList(ListNode* head) {
            if (!head) return head;
            ListNode *dummyHead = new ListNode(0); 
            ListNode *pre = dummyHead;
            ListNode *current = head;
            ListNode *next = nullptr;
            while (current) {
                next = current->next; 
                while (pre->next != nullptr && pre->next->val < current->val) {
                    pre = pre->next;
                }
                current->next = pre->next; 
                pre->next = current; 
                pre = dummyHead; 
                current = next; 
            }
            return dummyHead->next;
        }
    

    3.1.1 该题的设计思路###

    • 因为有insert操作,所有我们上来就定义三个指针pre、cur、lat。我们首先考虑2应该插在哪?
    • 我们发现2比tmp小,所以我们需要将2插入到tmp前。
    • 我们主要实现的操作就是
    • 接着我们需要将pre和tmp复位,同时我们需要移动cur和lat找到下一个需要被插入元素的位置。

      时间复杂度为O(n^2),空间复杂度为O(1)

    3.1.2 该题的伪代码###

           判断是否为空
            ListNode *dummyHead = new ListNode(0); //来一个虚拟头结点作为有序链表的头结点
            ListNode *pre = dummyHead;
            ListNode *current = head;
            ListNode *next = nullptr;
            while (current) {
                next记录下一个位置
                while (pre->next != nullptr && pre->next->val < current->val) {
                    pre = pre->next; //pre->next 位置就是需要插入元素的位置
                }
                end while
                把比current大的节点接到current后面
                将current接到合适的位置
                重置指针
                current = next; //接着比较下一个
            }
            end while
            return dummyHead->next;
        }
    

    3.1.3 运行结果###

    3.1.4分析该题目解题优势及难点。###

    • 优势:它巧妙的为新链表建了一个虚拟头结点dummyHead,dummyHead->next就是需要返回的链表。
    • 难点:由于是插入排序,存在一种可能使得最小的元素处于原链表中间甚至是末尾位置,此时对于新链表而言其头结点就会发生变化。

    3.2 题目及解题代码##

    给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
    k 是一个正整数,它的值小于或等于链表的长度。
    如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
    示例:
    给你这个链表:1->2->3->4->5
    当 k = 2 时,应当返回: 2->1->4->3->5
    当 k = 3 时,应当返回: 3->2->1->4->5

    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || head.next == null || k <= 1) {
            return head;
        }
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pointer = dummy;
    
        while (pointer != null) {
            ListNode lastGroup = pointer;
    
            int i = 0;            
            for (; i < k; ++i) {
                pointer = pointer.next;
                if (pointer == null) {
                    break;
                }
            }
    
            if (i == k) {
                ListNode nextGroup = pointer.next;
                ListNode reversedList = reverse(lastGroup.next, nextGroup);
                pointer = lastGroup.next;
                lastGroup.next = reversedList;
                pointer.next = nextGroup;
            }
        }
        return dummy.next;
    }
    private ListNode reverse(ListNode head, ListNode tail) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode prev = null, temp = null;
        while ((head != null) && (head != tail)) {
            temp = head.next;
            head.next = prev;
            prev = head;
            head = temp;
        }
    
        return prev;
    }
    

    3.2.1 该题的设计思路###


    时间复杂度为O(n*k),空间复杂度为O(1)。

    3.2.2 该题的伪代码###

    public ListNode reverseKGroup(ListNode head, int k) {
        判断是否为空
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pointer = dummy;
    
        while (pointer != null) {
            记录上一个子链表的尾
            int i = 0;            
            for (; i < k; ++i) {
                pointer = pointer.next;
                若为空,结束循环
            }
            end for
            如果当前子链表的节点数满足 k, 就进行反转
            反之,说明程序到尾了,节点数不够,不用反转
            if (i == k) {
                记录下一个子链表的头
                反转当前子链表,reverse 函数返回反转后子链表的头
                lastGroup 是上一个子链表的尾,其 next 指向当前反转子链表的头
                但是因为当前链表已经被反转,所以它指向的是反转后的链表的尾
                pointer = lastGroup.next;
                将上一个链表的尾连向反转后链表的头
                lastGroup.next = reversedList;
                当前反转后的链表的尾连向下一个子链表的头
                pointer.next = nextGroup;
            }
            end if
        }
        end while
        return dummy.next;
    }
    private ListNode reverse(ListNode head, ListNode tail) {
        判断是否为空
        ListNode prev 和temp 赋为空
        while ((head != null) && (head != tail)) {
            进行反转
        }
        end while
        return prev;
    }
    

    3.2.3 运行结果###

    3.2.4分析该题目解题优势及难点。###

    • 优势:把一个很长的链表分成很多个小链表,每一份的长度都是 k (最后一份的长度如果小于 k 则不需要反转),然后对每个小链表进行反转,最后将所有反转后的小链表按之前的顺序拼接在一起。在反转子链表的时候,上一个子链表的尾知道。下一个子链表的头也知道。当前反转的链表的头尾都知道。
    • 难点:其实链表的题目并不需要特别强的逻辑推理,它主要强调细节实现,难也是难在细节实现上面 ,虽然大致的方向知道,但是很可能写着写着就会乱。一般赋值的时候需要新建一个临时节点,可以存储即将移到最前端的那个节点,然后按照位置由后到前的顺序为各个变量的next赋值,写完要检查一下,某个下一行作为右值的变量,在上一行它的next是否被改变了?如果被改变了就要警惕,这可能不是正常的赋值顺序。
  • 相关阅读:
    java利用透明的图片轮廓抠图
    java单例之enum实现方式
    spring之ControllerAdvice注解
    memcached命令
    2016年开源巨献:来自百度的71款开源项目
    dubbo通信协议之对比
    Elasticsearch权威指南(中文版)
    Apache shiro之权限校验流程
    简单的freemarker解析测试
    Apache shiro之身份验证(登陆)流程
  • 原文地址:https://www.cnblogs.com/wangtianxue/p/12444415.html
Copyright © 2011-2022 走看看