Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

《剑指offer》刷题笔记(分解让复杂问题简单):复杂链表的复制

$
0
0

《剑指offer》刷题笔记(分解让复杂问题简单):复杂链表的复制



前言

在计算机领域有一类算法叫分治法,即“分而治之”。采用的就是各个击破的思想,我们把分解后的小问题各个解决,然后把小问题的解决方案结合起来解决大问题。

题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

解题思路

我们可以将复杂链表的复制过程分解为三个步骤。在写代码的时候我们每一步定义一个函数,这样每个函数完成一个功能,整个过程的逻辑也就非常清晰明了了。

大部分人首先想到的可能是先复制复杂指针的label和next,然后再查找random并更新。查找random又分为两种,一种是每次都从头查找,时间复杂度为O(n^2);另一种是空间换时间,复制label和next的同时建立一个hash表来存放新旧复杂指针的对应关系,所以后续只需一步就能找到random,算法时间复杂度为O(n)。

我们这里采用三步走战略,也是剑指offer上推崇的方法:

  • 第一步:复制复杂指针的label和next。但是这次我们把复制的结点跟在元结点后面,而不是直接创建新的链表;
  • 第二步:设置复制出来的结点的random。因为新旧结点是前后对应关系,所以也是一步就能找到random;
  • 第三步:拆分链表。奇数是原链表,偶数是复制的链表。

有图思路更清晰:

C++版代码实现

/*
/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    //第一步,复制复杂指针的label和next
    void CloneNodes(RandomListNode* pHead){
        RandomListNode* pNode = pHead;
        while(pNode != NULL){
            RandomListNode* pCloned = new RandomListNode(0);
            pCloned->label = pNode->label;
            pCloned->next = pNode->next;
            pCloned->random = NULL;

            pNode->next = pCloned;
            pNode = pCloned->next;
        }
    }
    //第二步,处理复杂指针的random
    void ConnectSiblingNodes(RandomListNode* pHead){
        RandomListNode* pNode = pHead;
        while(pNode != NULL){
            RandomListNode* pCloned = pNode->next;
            if(pNode->random != NULL)
                pCloned->random = pNode->random->next;
            pNode = pCloned->next;
        }
    }
    //第三步,拆分复杂指针
    RandomListNode* ReconnectNodes(RandomListNode* pHead){
        RandomListNode* pNode = pHead;
        RandomListNode* pCloneHead = NULL;
        RandomListNode* pCloneNode = NULL;
        if(pNode != NULL){
            pCloneHead = pCloneNode = pNode->next;
            pNode->next = pCloneNode->next;
            pNode = pNode->next;
        }
        while(pNode != NULL){
            pCloneNode->next = pNode->next;
            pCloneNode = pCloneNode->next;
            pNode->next = pCloneNode->next;
            pNode = pNode->next;
        }
        return pCloneHead;
    }

    RandomListNode* Clone(RandomListNode* pHead)
    {
        CloneNodes(pHead);
        ConnectSiblingNodes(pHead);
        return ReconnectNodes(pHead);
    }
};

Python版代码实现

# -*- coding:utf-8 -*-
# class RandomListNode:
#     def __init__(self, x):
#         self.label = x
#         self.next = None
#         self.random = None
class Solution:
    #RandomListNode
    def CloneNodes(self, pHead):
        pNode = pHead
        while pNode is not None:
            pCloned = RandomListNode(0)
            pCloned.label = pNode.label
            pCloned.next = pNode.next
            pCloned.random = None

            pNode.next = pCloned
            pNode = pCloned.next

    def ConnectSiblingNodes(self, pHead):
        pNode = pHead
        while pNode is not None:
            pCloned = pNode.next
            if pNode.random is not None:
                pCloned.random = pNode.random.next
            pNode = pCloned.next

    def ReconnectNodes(self, pHead):
        pNode = pHead
        pCloneHead = None
        if pNode is not None:
            pCloneHead = pCloneNode = pNode.next
            pNode.next = pCloneNode.next
            pNode = pNode.next
        while pNode is not None:
            pCloneNode.next = pNode.next
            pCloneNode = pCloneNode.next
            pNode.next = pCloneNode.next
            pNode = pNode.next
        return pCloneHead

    def Clone(self, pHead):
        # write code here
        self.CloneNodes(pHead)
        self.ConnectSiblingNodes(pHead)
        return self.ReconnectNodes(pHead)

系列教程持续发布中,欢迎订阅、关注、收藏、评论、点赞哦~~( ̄▽ ̄~)~

完的汪(∪。∪)。。。zzz

作者:u011475210 发表于2017/11/8 13:31:11 原文链接
阅读:36 评论:0 查看评论

Quartz-Java Web项目中使用Quartz

$
0
0

概述

Quartz也常用在Web应用中,常见的是交由Spring托管的形式,但这里并非介绍这个。如果你的很老的一个项目没有使用Spring呢? 这里我们介绍Quartz在Web应用中单独使用的场景。


实现

对于定时任务来讲,一般来说,Web应用启动时,应注册已经确定的定时任务;一些动态的、未确定触发时间的定时任务,后续可通过静态的Scheduler注册。

这里使用监听器在应用启动时注册,需要在web.xml注册这个监听器,在关闭Web应用时,也要相应的注销定时任务。


示例

maven工程

这里写图片描述


步骤一 构建Maven项目

pom.xml中添加依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.artisan</groupId>
    <artifactId>quartzInWeb</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>quartzInWeb Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.3</version>
        </dependency>

        <!-- 日志组件slf4j xml的方式需增加 logback -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.1.7</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.7</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

    </dependencies>
    <build>
        <finalName>quartzInWeb</finalName>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>

        </plugins>
    </build>
</project>

步骤二 日志组件的配置logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 应用名称 -->
    <property name="APP_NAME" value="logtest" />
    <!--日志文件的保存路径,首先查找系统属性-Dlog.dir,如果存在就使用其;否则,在当前目录下创建名为logs目录做日志存放的目录 -->
    <property name="LOG_HOME" value="${log.dir:-logs}/${APP_NAME}" />
    <!-- 日志输出格式 -->
    <property name="ENCODER_PATTERN"
              value="%d{yyyy-MM-dd  HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n" />
    <contextName>${APP_NAME}</contextName>

    <!-- 控制台日志:输出全部日志到控制台 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>${ENCODER_PATTERN}</Pattern>
        </encoder>
    </appender>

    <!-- 文件日志:输出全部日志到文件 -->
    <appender name="FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/output.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${ENCODER_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- 错误日志:用于将错误日志输出到独立文件 -->
    <appender name="ERROR_FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${ENCODER_PATTERN}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
    </appender>

    <!-- 独立输出的同步日志 -->
    <appender name="SYNC_FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/sync.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${ENCODER_PATTERN}</pattern>
        </encoder>
    </appender>

    <logger name="log.sync" level="DEBUG" addtivity="true">
        <appender-ref ref="SYNC_FILE" />
    </logger>

    <root>
        <level value="DEBUG" />
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
        <appender-ref ref="ERROR_FILE" />
    </root>
</configuration>

步骤三 自定义监听器的编写

package com.artisan.quartz;

import static org.quartz.JobBuilder.newJob;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ApplicationContextListener implements ServletContextListener {
    private Logger logger = LoggerFactory
            .getLogger(ApplicationContextListener.class);

    public static Scheduler scheduler = null;

    public void contextInitialized(ServletContextEvent servletContextEvent) {
        logger.info("Web应用开始...");

        /* 注册定时任务 */
        try {
            // 获取Scheduler实例
            scheduler = StdSchedulerFactory.getDefaultScheduler();
            scheduler.start();

            // 具体任务
            JobDetail job = newJob(HelloJob.class)
                    .withIdentity("job1", "group1").build();

            // 触发时间点
            SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder
                    .simpleSchedule().withIntervalInSeconds(5).repeatForever();

            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1").startNow()
                    .withSchedule(simpleScheduleBuilder).build();

            // 交由Scheduler安排触发
            scheduler.scheduleJob(job, trigger);

            logger.info("调度器开始注册:The scheduler register...");
        } catch (SchedulerException se) {
            logger.error(se.getMessage(), se);
        }
    }

    public void contextDestroyed(ServletContextEvent sce) {
        logger.info("Web应用停止...");

        /* 注销定时任务 */
        try {
            // 关闭Scheduler
            scheduler.shutdown();

            logger.info("调度器已关闭:The scheduler shutdown...");
        } catch (SchedulerException se) {
            logger.error(se.getMessage(), se);
        }
    }

}

假设我们有一个明确的任务,在初始化监听器的时候就启动执行,如下

package com.artisan.quartz;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloJob implements Job {

    private Logger logger = LoggerFactory.getLogger(HelloJob.class);

    @Override
    public void execute(JobExecutionContext context)
            throws JobExecutionException {
        System.out.println("Hello Job");
        // 此任务仅打印日志便于调试、观察
        System.out.println(this.getClass().getSimpleName() + " trigger...");
        logger.debug(this.getClass().getSimpleName() + " trigger...");
    }

}

步骤四 web.xml中注册监听器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <!-- 加入自定义监听器 -->
    <listener>
        <listener-class>com.artisan.quartz.ApplicationContextListener</listener-class>
    </listener>

</web-app>

步骤五 启动

由于我们使用的JDK1.7 ,我们用的tomcat,这里tomcat的版本需要为8.0

这里写图片描述

关键日志如下:

这里写图片描述

如果我们Eclipse或者Spring tool suit中调试,无法看到contextDestroyed方法的执行。

SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever();

repeatForever()方法,这个方法的意思表示永远执行,当然我们也可以自定义重复执行的次数,使用withRepeatCount(10)方法,10表示执行了10次

作者:yangshangwei 发表于2017/11/8 13:51:19 原文链接
阅读:1 评论:0 查看评论

关联分析之Apriori算法

$
0
0

本文demo源码、实验数据:传送门

引言

如题,关联分析这个词语对于初学者而言或许比较陌生。但是我若将关联分析换成另一个短语“尿布与啤酒”大家就会很熟悉了。据报道,美国中西部的一家连锁店发现,男人们会在周四购买尿布和啤酒。这样商店实际上可以将尿布和啤酒放在一块,并确保在周四的销售中获利。“尿布与啤酒”是关联分析中最著名的例子。那么关联分析的定义也就呼之欲出了:从大规模数据集中寻找物品间的隐含关系被称作关联分析或者关联规则学习。但是寻找物品之间的隐含关系是一项十分耗时的任务,所需的计算代价很高,这就催生出了Apriori算法来在合理的时间范围内解决上述问题。

频繁项集、关联分析

关联分析是一种在大规模数据集中寻找有趣关系的任务。这些关系可以有两种形式:频繁项集或关联规则。频繁项集是经常出现在一起的物品的集合,关联规则按时两种物品之间可能存在很强的关系。我们来举个例子来说明这两个概念。下图给出了某个杂货店的交易清单。
grocery store
频繁项是指那些经常出现在一起的物品集合,如上图所示的{diapers,wine}就是频繁项集的一个例子。从上图中我们也能找到诸如diaperswine的关联规则。这意味着如果有人买了diapers,那么他很可能也会买wine。

量化的方法–支持度、置信度

如何判断数据集中存在这种关系,如何量化数据从而得到判断这些关系是否存在的标准呢?有人提出了支持度(support)和置信度(confidence)的概念。
一个项集的支持度被定义为数据集中包含该项的记录所占的比例。如上图所示,{soy milk}的支持度为4/5。支持度是针对项集来说的,因此可以定义一个最小支持度,而只保留满足最小支持度的项集。置信度是针对诸如diaperswine的关联规则来定义的。这条规则的置信度被定义为:支持度({diapers,wine})/支持度({diapers})。这一定义也可以用条件概率来解释:

P(diapers,wine)=P(wine|diapers)P(diapers)

大家看到这个条件概率是不是很惊奇?其实从apriori这里我们就能发现一丝端倪了。Apriori在拉丁语中指”来自以前”。当定义问题时,通常会使用先验知识或者假设,这被称作“一个先验”(apriori)。在贝叶斯统计中,使用先验知识作为条件进行推断也很正常。
支持度和置信度是用来量化关联分析是否成功的方法。假设想找到支持度大于0.8的所有项集,应该如何去做?或许有人会想到“贪心”的方法,就是生成一个物品所有可能组合的清单,然后对每一种组合统计它出现的频繁程度,但是当物品成千上万时,上述做法就需要消耗大量的计算资源。因此有人提出了Apriori算法来减少关联规则学习时所需的计算量。

Apriori原理

我们以经营一家商品种类并不多的杂货店为例。我们总共有四种商品:商品0,商品1,商品2,商品3。那么这四种商品所有可能的项集组合如下所示:
possible
对于仅有4种物品的集合,也要有15种项集组合。对于N种物品的集合,就有2N1种项集组合。
这种项集组合个数指数型增长将会消耗极大的计算资源。那么,到底有没有一种方法能够降低所需要的计算时间呢?

Apriori原理

研究人员发现了一种所谓的Apriori原理。这种原理可以帮助我们减少可能感兴趣的项集。为什么Apriori原理能够减少计算时间呢?那是因为在数据中,如果某个项集是频繁的,那么它的所有子集都是频繁地。反之,如果一个项集是非频繁的,那么它的所有超集也》
association
上图给出了所有可能的项集,其中非频繁项用灰色表示。由于集合{2,3}是非频繁的,因此{0,2,3}、{1,2,3}、{0,1,2,3}也是非频繁的,它们的支持度根本不需要计算。

使用Apriori算法来发现频繁项集

前面我们有说,关联分析的目标包括两项:发现频繁项集和发现关联规则。首先需要先找到频繁项集,然后才能获得关联规则。
Apriori算法是发现频繁项集的一种方法。Apriori算法的两个输入参数分别是最小支持度和数据集。该算法首先会生成所有单个物品的项集列表。接着扫描交易记录来查看哪些项集满足最小支持度的要求,那些不满足最小支持度的集合会被去掉。然后,对剩下来的集合进行组合以生成包含两个元素的项集。接下来,再重新扫描交易记录,去掉不满足最小支持度的项集。该过程重复进行直到满足退出条件。

生成候选项集

下面会创建一个用于构建初始集合的函数,也会创建一个通过扫描数据集以寻找交易记录子集的函数。数据集扫描的伪代码如下:
这里写图片描述
实际代码如下:

def loadDataSet():
    return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]

def createC1(dataSet):
    C1 = []
    for transaction in dataSet:
        for item in transaction:
            if not [item] in C1:
                C1.append([item])

    C1.sort()
    return list(map(frozenset, C1))#use frozen set so we
                            #can use it as a key in a dict    

def scanD(D, Ck, minSupport):
    ssCnt = {}
    for tid in D:
        for can in Ck:
            if can.issubset(tid):
                if not can in ssCnt: ssCnt[can]=1
                else: ssCnt[can] += 1
    numItems = float(len(D))
    retList = []
    supportData = {}
    for key in ssCnt:
        support = ssCnt[key]/numItems
        if support >= minSupport:
            retList.insert(0,key)
        supportData[key] = support
    return retList, supportData

我们一共有三个函数,第一个函数loadDataSet()
用来构建交易记录,一共四条交易记录,商品种类一共5种。第二个函数是createC1()这里C1是指大小为1的所有候选项集的集合。第三个函数scanD(),它有三个参数,分别是数据集、候选集列表Ck以及感兴趣项集的最小支持度minSupport。
我们来看实际的运行效果:

import importlib
import apriori
dataSet = apriori.loadDataSet()
dataSet
[[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
importlib.reload(apriori) 
C1 = apriori.createC1(dataSet)
C1
[frozenset({1}),
 frozenset({2}),
 frozenset({3}),
 frozenset({4}),
 frozenset({5})]
L1 ,suppData0  = apriori.scanD(dataSet,C1,0.5)
L1
[frozenset({5}), frozenset({2}), frozenset({3}), frozenset({1})]

上述四个项集构成了L1列表,该列表的每个但物品项集至少出现在50%以上的记录中。这通过去掉物品4,从而为接下来寻找两两物品组合的项集减少了工作量。

完整的Apriori算法

整个Apriori算法的伪代码如下:
algorithm
完整的代码如下:

def aprioriGen(Lk, k): #creates Ck
    retList = []
    lenLk = len(Lk)
    for i in range(lenLk):
        for j in range(i+1, lenLk): 
            L1 = list(Lk[i])[:k-2]; L2 = list(Lk[j])[:k-2]
            L1.sort(); L2.sort()
            if L1==L2: #if first k-2 elements are equal
                retList.append(Lk[i] | Lk[j]) #set union
    return retList

def apriori(dataSet, minSupport = 0.5):
    C1 = createC1(dataSet)
    D = dataSet
    L1, supportData = scanD(D, C1, minSupport)
    L = [L1]
    k = 2
    while (len(L[k-2]) > 0):
        Ck = aprioriGen(L[k-2], k)
        Lk, supK = scanD(D, Ck, minSupport)#scan DB to get Lk
        supportData.update(supK)
        L.append(Lk)
        k += 1
    return L, supportData

上述代码由两个函数组成,主函数是apriori(),它会调用aprioriGen()来创建候选集Ck。其中有句代码需要注意下:

L1 = list(Lk[i])[:k-2]; L2 = list(Lk[j])[:k-2]

这里的k-2或许大家不是很清楚,k是指要生成的元素个数,假设有项集{0,1},{0,2},{1,2}那么如果两两集合合并,就会得到重复项,为了减少遍历列表时的时间复杂度,我们发现如果比较集合的前k-2个元素是否相等,相等便合并,不想等不合并。按这个规则合并,不仅结果一样,而且没有重复计算的过程。
具体实验结果如下:

##apriori algorithm
importlib.reload(apriori) 
L,suppData = apriori.apriori(dataSet)
L
[[frozenset({5}), frozenset({2}), frozenset({3}), frozenset({1})],
 [frozenset({2, 3}), frozenset({3, 5}), frozenset({2, 5}), frozenset({1, 3})],
 [frozenset({2, 3, 5})],
 []]
L[0]
[frozenset({5}), frozenset({2}), frozenset({3}), frozenset({1})]
L[1]
[frozenset({2, 3}), frozenset({3, 5}), frozenset({2, 5}), frozenset({1, 3})]
L[2]
[frozenset({2, 3, 5})]
L[3]
[]

从频繁项集中挖掘关联规则

前面我们介绍了Apriori原理,然后实现了Apriori算法来发现频繁项集,现在我们就来找出关联规则。要想找到关联规则,首先就要从频繁项集入手。我们希望从某个元素或者某个元素集合去推导出另一个元素或集合。
从杂货店的例子,我们可以得到如果存在一个频繁项集{diapers,wine},那么就可能有一条关联规则“diaperwine”,这意味着如果有人购买了diapers,那么他购买wine的概率会很大。但是反之并不一定成立。我们前面给出了关联规则置信度的量化定义。在这里一条规则
“diaperwine”的置信度定义为support(diaper,wine)/support(diapers)。所以现在看来,要想求关联规则,就先获取置信度,而置信度通过公式就是将前面求得的支持度做了个除法。
那么从一个频繁项集中,能够生成多少规则呢?我们假设频繁项集{0,1,2,3}那么下图给出了关联规则网格示意图。
grid
与频繁项集的生成类似,我们也能找到一个原理来减少规则数目从而降低计算量。图中阴影部分给出了低置信度的规则,可以观察得到,如果某条规则并不满足最小置信度要求,那么该规则的所有子集也不会满足最小置信度要求。与前面讲述的apriori算法类似,首先从一个频繁项集开始,接着创建一个规则列表,其中规则右部只包含元素,然后对这些规则进行测试。接下来合并所有剩余规则来创建一个新的规则列表,其中规则右部包含两个元素。这种方法也被称作分级法。
下面我们直接给出该算法的实现代码:

def generateRules(L, supportData, minConf=0.7):  #supportData is a dict coming from scanD
    bigRuleList = []
    for i in range(1, len(L)):#only get the sets with two or more items
        for freqSet in L[i]:
            H1 = [frozenset([item]) for item in freqSet]
            if (i > 1):
                rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
            else:
                calcConf(freqSet, H1, supportData, bigRuleList, minConf)
    return bigRuleList         

def calcConf(freqSet, H, supportData, brl, minConf=0.7):
    prunedH = [] #create new list to return
    for conseq in H:
        conf = supportData[freqSet]/supportData[freqSet-conseq] #calc confidence
        if conf >= minConf: 
            print(freqSet-conseq,'-->',conseq,'conf:',conf)
            brl.append((freqSet-conseq, conseq, conf))
            prunedH.append(conseq)
    return prunedH

def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):
    m = len(H[0])
    if (len(freqSet) > (m + 1)): #try further merging
        Hmp1 = aprioriGen(H, m+1)#create Hm+1 new candidates
        Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)
        if (len(Hmp1) > 1):    #need at least two sets to merge
            rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)

上述代码一共有三个函数,其中第一个函数generateRules()是主函数,其余两个函数rulesFromConseq()和calcConf分别用于生成候选规则集合以及对规则进行评估。
看一下实际运行效果:

##association
##生成一个最小支持度为0.5的频繁项的集合
L,suppData = apriori.apriori(dataSet,minSupport=0.5)
rules =apriori.generateRules(L,suppData,minConf=0.7)
rules
frozenset({5}) --> frozenset({2}) conf: 1.0
frozenset({2}) --> frozenset({5}) conf: 1.0
frozenset({1}) --> frozenset({3}) conf: 1.0

[(frozenset({5}), frozenset({2}), 1.0),
 (frozenset({2}), frozenset({5}), 1.0),
 (frozenset({1}), frozenset({3}), 1.0)]

总结

关联分析是用于发现大数据集中元素间有趣关系的一个工具集,可以采用两种方式来量化这些有趣的关系。第一种是频繁项集,第二种是关联规则。发现元素项间不同的组合是个十分耗时的任务,这就需要一些更智能的方法,比如Apriori算法来降低时间复杂度。但是这种算法还是存在问题的,因为在每次增加频繁项集的大小,Apriori都会重新扫描整个数据集。当数据集很大时,这回显著降低频繁项集发现的速度。FP-growth算法就能很好的解决这个问题。

作者:u010665216 发表于2017/11/8 14:03:41 原文链接
阅读:37 评论:0 查看评论

BZOJ1179 [Apio2009]Atm

$
0
0

标签:SPFA,tarjan缩点

Description


Input

第一行包含两个整数N、M。N表示路口的个数,M表示道路条数。接下来M行,每行两个整数,这两个整数都在1到N之间,第i+1行的两个整数表示第i条道路的起点和终点的路口编号。接下来N行,每行一个整数,按顺序表示每个路口处的ATM机中的钱数。接下来一行包含两个整数S、P,S表示市中心的编号,也就是出发的路口。P表示酒吧数目。接下来的一行中有P个整数,表示P个有酒吧的路口的编号

Output

输出一个整数,表示Banditji从市中心开始到某个酒吧结束所能抢劫的最多的现金总数。

Sample Input

6 7
1 2
2 3
3 5
2 4
4 1
2 6
6 5
10
12
8
16
1 5
1 4
4
3
5
6

Sample Output

47

HINT

50%的输入保证N,M<=3000。所有的输入保证N, M<=500000。每个ATM机中可取的钱数为一个非负整数且不超过4000。输入数据保证你可以从市中心沿着Siruseri的单向的道路到达其中的至少一个酒吧。

 

算法很简单

Tarjan缩点重构图后跑一遍SPFA最长路就可以了

但是代码实现很复杂,很多细节,容易写挂题

SPFA中的初始点S,一定要赋值为belong[s]

剩下最短路中的,因为重构图,所以可以直接指向节点本身

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,a,b) for(register int i=a;i<=b;i++)
#define dep(i,a,b) for(register int i=a;i>=b;i--)
#define ll long long
#define mem(x,num) memset(x,num,sizeof x)
#ifdef WIN32
#define LL "%I64d\n"
#else
#define LL "%lld\n"
#endif
using namespace std;
inline ll read()
{
    ll f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
const int maxm=5e5+6,maxn=5e5+6;
int inq[maxn],que[maxn<<1],dfn[maxn],c[maxn],low[maxn],dis[maxn],v[maxn],tim=0,cnt=0,tot=0,top=0;
int last[maxn],rlast[maxn],belong[maxn];
int n,m,s,p;
struct edge{int to,next;}e[maxm<<1],re[maxm<<1];
#define reg(x) for(int i=last[x];i;i=e[i].next)
#define ov e[i].to
#define newreg(x) for(int i=rlast[x];i;i=re[i].next)
#define rv re[i].to

void tarjan(int x)
{
	int now=0;
	dfn[x]=low[x]=++tim;
	que[++top]=x;inq[x]=1;
	reg(x)
	    if(!dfn[ov]){tarjan(ov);low[x]=min(low[x],low[ov]);}
	    else if(inq[ov])low[x]=min(low[x],dfn[ov]);
	if(low[x]==dfn[x]){
		tot++;
		while(now!=x){
			now=que[top--];
			belong[now]=tot;
			v[tot]+=c[now];
			inq[now]=0;
		}
	}
}
void rebuild()
{
	cnt=0;
	rep(k,1,n)
	    reg(k)
	        if(belong[k]!=belong[ov]){
			    re[++cnt]=(edge){belong[k],rlast[belong[ov]]};rlast[belong[ov]]=cnt;
				//re[++cnt]=(edge){belong[ov],rlast[belong[k]]};rlast[belong[k]]=cnt;
			}
}

void spfa()
{
	int head=1,tail=1;
	que[head]=belong[s];inq[belong[s]]=1;dis[belong[s]]=v[belong[s]];
	while(head<=tail){
		int now=que[head++];
		newreg(now)
			if(dis[now]+v[rv]>dis[rv]){
				dis[rv]=dis[now]+v[rv];
				if(!inq[rv]){
					inq[rv]=1;
					que[++tail]=rv;
				}
			}
		inq[now]=0;
	}
}
	
			
int main()
{
	n=read(),m=read();
	rep(i,1,m){
		int x=read(),y=read();
		e[++cnt]=(edge){x,last[y]};last[y]=cnt;
		//e[++cnt]=(edge){y,last[x]};last[x]=cnt;
	}
	rep(i,1,n)c[i]=read();
	rep(i,1,n)if(!dfn[i])tarjan(i);
	s=read(),p=read();
	rebuild();
	spfa();
	int ans=0;
	rep(i,1,p){
		int x=read();
		ans=max(ans,dis[belong[x]]);
	}
	cout<<ans<<endl;
	return 0;
}


作者:qwerty1125 发表于2017/11/8 14:21:25 原文链接
阅读:25 评论:0 查看评论

Go语言学习之expvar包(公共变量)(the way to go)

$
0
0

生命不止,继续 go go go!!!

基础还是要打好,很久没有分享golang的标准包了,今天就来一个expvar包。

Package expvar

概述
Package expvar provides a standardized interface to public variables, such as operation counters in servers. It exposes these variables via HTTP at /debug/vars in JSON format.
Operations to set or modify these public variables are atomic.

In addition to adding the HTTP handler, this package registers the following variables:

cmdline   os.Args
memstats  runtime.Memstats

The package is sometimes only imported for the side effect of registering its HTTP handler and the above variables. To use it this way, link this package into your program:

import _ "expvar"

expvar包提供了公共变量的标准接口,如服务的操作计数器。本包通过HTTP在/debug/vars位置以JSON格式导出了这些变量。+
对这些公共变量的读写操作都是原子级的。
为了增加HTTP处理器,本包注册了如下变量:+

cmdline   os.Args
memstats  runtime.Memstats

有时候本包被导入只是为了获得本包注册HTTP处理器和上述变量的副作用。此时可以如下方式导入本包:

import _ "expvar"

支持类型
支持一些常见的类型:float64、int64、Map、String。
如果我们的程序要用到上面提的四种类型(其中,Map 类型要求 Key 是字符串)。可以考虑使用这个包。

功能
支持对变量的基本操作,修改、查询

整形类型,可以用来做计数器

线程安全的

此外还提供了调试接口,/debug/vars。它能够展示所有通过这个包创建的变量

所有的变量都是Var类型,可以自己通过实现这个接口扩展其它的类型

Handler()方法可以得到调试接口的http.Handler,和自己的路由对接

func Do

func Do(f func(KeyValue))

Do calls f for each exported variable. The global variable map is locked during the iteration, but existing entries may be concurrently updated.
Do对映射的每一条记录都调用f。迭代执行时会锁定该映射,但已存在的记录可以同时更新。

func Handler

func Handler() http.Handler

Handler returns the expvar HTTP Handler.

This is only needed to install the handler in a non-standard location.

func Publish

func Publish(name string, v Var)

Publish declares a named exported variable. This should be called from a package’s init function when it creates its Vars. If the name is already registered then this will log.Panic.
Publish声明一个导出变量。必须在init函数里调用。如果name已经被注册,会调用log.Panic。

源码
由于expvar的源码篇幅不是很大,就贴上了:

package expvar

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
    "math"
    "net/http"
    "os"
    "runtime"
    "sort"
    "strconv"
    "sync"
    "sync/atomic"
)

// Var is an abstract type for all exported variables.
type Var interface {
    // String returns a valid JSON value for the variable.
    // Types with String methods that do not return valid JSON
    // (such as time.Time) must not be used as a Var.
    String() string
}

// Int is a 64-bit integer variable that satisfies the Var interface.
type Int struct {
    i int64
}

func (v *Int) Value() int64 {
    return atomic.LoadInt64(&v.i)
}

func (v *Int) String() string {
    return strconv.FormatInt(atomic.LoadInt64(&v.i), 10)
}

func (v *Int) Add(delta int64) {
    atomic.AddInt64(&v.i, delta)
}

func (v *Int) Set(value int64) {
    atomic.StoreInt64(&v.i, value)
}

// Float is a 64-bit float variable that satisfies the Var interface.
type Float struct {
    f uint64
}

func (v *Float) Value() float64 {
    return math.Float64frombits(atomic.LoadUint64(&v.f))
}

func (v *Float) String() string {
    return strconv.FormatFloat(
        math.Float64frombits(atomic.LoadUint64(&v.f)), 'g', -1, 64)
}

// Add adds delta to v.
func (v *Float) Add(delta float64) {
    for {
        cur := atomic.LoadUint64(&v.f)
        curVal := math.Float64frombits(cur)
        nxtVal := curVal + delta
        nxt := math.Float64bits(nxtVal)
        if atomic.CompareAndSwapUint64(&v.f, cur, nxt) {
            return
        }
    }
}

// Set sets v to value.
func (v *Float) Set(value float64) {
    atomic.StoreUint64(&v.f, math.Float64bits(value))
}

// Map is a string-to-Var map variable that satisfies the Var interface.
type Map struct {
    m      sync.Map // map[string]Var
    keysMu sync.RWMutex
    keys   []string // sorted
}

// KeyValue represents a single entry in a Map.
type KeyValue struct {
    Key   string
    Value Var
}

func (v *Map) String() string {
    var b bytes.Buffer
    fmt.Fprintf(&b, "{")
    first := true
    v.Do(func(kv KeyValue) {
        if !first {
            fmt.Fprintf(&b, ", ")
        }
        fmt.Fprintf(&b, "%q: %v", kv.Key, kv.Value)
        first = false
    })
    fmt.Fprintf(&b, "}")
    return b.String()
}

// Init removes all keys from the map.
func (v *Map) Init() *Map {
    v.keysMu.Lock()
    defer v.keysMu.Unlock()
    v.keys = v.keys[:0]
    v.m.Range(func(k, _ interface{}) bool {
        v.m.Delete(k)
        return true
    })
    return v
}

// updateKeys updates the sorted list of keys in v.keys.
func (v *Map) addKey(key string) {
    v.keysMu.Lock()
    defer v.keysMu.Unlock()
    v.keys = append(v.keys, key)
    sort.Strings(v.keys)
}

func (v *Map) Get(key string) Var {
    i, _ := v.m.Load(key)
    av, _ := i.(Var)
    return av
}

func (v *Map) Set(key string, av Var) {
    // Before we store the value, check to see whether the key is new. Try a Load
    // before LoadOrStore: LoadOrStore causes the key interface to escape even on
    // the Load path.
    if _, ok := v.m.Load(key); !ok {
        if _, dup := v.m.LoadOrStore(key, av); !dup {
            v.addKey(key)
            return
        }
    }

    v.m.Store(key, av)
}

// Add adds delta to the *Int value stored under the given map key.
func (v *Map) Add(key string, delta int64) {
    i, ok := v.m.Load(key)
    if !ok {
        var dup bool
        i, dup = v.m.LoadOrStore(key, new(Int))
        if !dup {
            v.addKey(key)
        }
    }

    // Add to Int; ignore otherwise.
    if iv, ok := i.(*Int); ok {
        iv.Add(delta)
    }
}

// AddFloat adds delta to the *Float value stored under the given map key.
func (v *Map) AddFloat(key string, delta float64) {
    i, ok := v.m.Load(key)
    if !ok {
        var dup bool
        i, dup = v.m.LoadOrStore(key, new(Float))
        if !dup {
            v.addKey(key)
        }
    }

    // Add to Float; ignore otherwise.
    if iv, ok := i.(*Float); ok {
        iv.Add(delta)
    }
}

// Do calls f for each entry in the map.
// The map is locked during the iteration,
// but existing entries may be concurrently updated.
func (v *Map) Do(f func(KeyValue)) {
    v.keysMu.RLock()
    defer v.keysMu.RUnlock()
    for _, k := range v.keys {
        i, _ := v.m.Load(k)
        f(KeyValue{k, i.(Var)})
    }
}

// String is a string variable, and satisfies the Var interface.
type String struct {
    s atomic.Value // string
}

func (v *String) Value() string {
    p, _ := v.s.Load().(string)
    return p
}

// String implements the Val interface. To get the unquoted string
// use Value.
func (v *String) String() string {
    s := v.Value()
    b, _ := json.Marshal(s)
    return string(b)
}

func (v *String) Set(value string) {
    v.s.Store(value)
}

// Func implements Var by calling the function
// and formatting the returned value using JSON.
type Func func() interface{}

func (f Func) Value() interface{} {
    return f()
}

func (f Func) String() string {
    v, _ := json.Marshal(f())
    return string(v)
}

// All published variables.
var (
    vars      sync.Map // map[string]Var
    varKeysMu sync.RWMutex
    varKeys   []string // sorted
)

// Publish declares a named exported variable. This should be called from a
// package's init function when it creates its Vars. If the name is already
// registered then this will log.Panic.
func Publish(name string, v Var) {
    if _, dup := vars.LoadOrStore(name, v); dup {
        log.Panicln("Reuse of exported var name:", name)
    }
    varKeysMu.Lock()
    defer varKeysMu.Unlock()
    varKeys = append(varKeys, name)
    sort.Strings(varKeys)
}

// Get retrieves a named exported variable. It returns nil if the name has
// not been registered.
func Get(name string) Var {
    i, _ := vars.Load(name)
    v, _ := i.(Var)
    return v
}

// Convenience functions for creating new exported variables.

func NewInt(name string) *Int {
    v := new(Int)
    Publish(name, v)
    return v
}

func NewFloat(name string) *Float {
    v := new(Float)
    Publish(name, v)
    return v
}

func NewMap(name string) *Map {
    v := new(Map).Init()
    Publish(name, v)
    return v
}

func NewString(name string) *String {
    v := new(String)
    Publish(name, v)
    return v
}

// Do calls f for each exported variable.
// The global variable map is locked during the iteration,
// but existing entries may be concurrently updated.
func Do(f func(KeyValue)) {
    varKeysMu.RLock()
    defer varKeysMu.RUnlock()
    for _, k := range varKeys {
        val, _ := vars.Load(k)
        f(KeyValue{k, val.(Var)})
    }
}

func expvarHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json; charset=utf-8")
    fmt.Fprintf(w, "{\n")
    first := true
    Do(func(kv KeyValue) {
        if !first {
            fmt.Fprintf(w, ",\n")
        }
        first = false
        fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
    })
    fmt.Fprintf(w, "\n}\n")
}

// Handler returns the expvar HTTP Handler.
//
// This is only needed to install the handler in a non-standard location.
func Handler() http.Handler {
    return http.HandlerFunc(expvarHandler)
}

func cmdline() interface{} {
    return os.Args
}

func memstats() interface{} {
    stats := new(runtime.MemStats)
    runtime.ReadMemStats(stats)
    return *stats
}

func init() {
    http.HandleFunc("/debug/vars", expvarHandler)
    Publish("cmdline", Func(cmdline))
    Publish("memstats", Func(memstats))
}

简单应用

例1
main.go

package main

import (
    "expvar"
    "net"
    "net/http"
)

var (
    test = expvar.NewMap("Test")
)

func init() {
    test.Add("go", 10)
    test.Add("go1", 10)
}

func main() {
    sock, err := net.Listen("tcp", "localhost:8080")
    if err != nil {
        panic("error")
    }
    go func() {
        http.Serve(sock, nil)
    }()
    select {}
}

浏览器访问:http://localhost:8080/debug/vars
这里写图片描述

例2
main.go

package main

import (
    "expvar"
    "io"
    "net/http"
    "strconv"
    "time"

    "github.com/paulbellamy/ratecounter"
)

var (
    counter       *ratecounter.RateCounter
    hitsperminute = expvar.NewInt("hits_per_minute")
)

func increment(w http.ResponseWriter, r *http.Request) {
    counter.Incr(1)
    hitsperminute.Set(counter.Rate())
    io.WriteString(w, strconv.FormatInt(counter.Rate(), 10))
}

func main() {
    counter = ratecounter.NewRateCounter(1 * time.Minute)
    http.HandleFunc("/increment", increment)
    http.ListenAndServe(":8000", nil)
}

浏览器输入:http://localhost:8000/increment
curl命令行访问: curl http://localhost:8000/increment

浏览器访问:http://localhost:8000/debug/vars
这里写图片描述

完美例子

https://golang.org/src/net/http/triv.go?m=text

延伸

https://github.com/divan/expvarmon
之后会有详细介绍

作者:wangshubo1989 发表于2017/11/8 15:22:04 原文链接
阅读:25 评论:0 查看评论

Advanced Programming in UNIX Environment Episode 13

$
0
0

文件共享
内核使用3中数据结构表示打开文件,他们之间的关系决定了文件共享方面的一个进程对另一个进程可能产生的影响。
(1)每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述相关联的是:
a. 文件描述符标识(close_on_exec);
b. 指向一个文件表项的指针。
(2)内核为所有打开文件维持一张文件表。每个文件表项包含:
a. 文件状态标志(读、写、添写、同步和非阻塞等);
b. 当前文件偏移量;
c. 指向文件v节点表项的指针。
(3)每个打开文件(或设备)都有一个v节点(v-node)结构。

Linux没有使用v节点,而是使用了通用i节点结构。虽然两种实现有所不同,但在概念上v节点与i节点是一样的。两者都指向文件系统特有的i节点结构。

假定第一个进程在文件描述符3上打开该文件,而另一个进程在文件描述符4上打开该文件。

  • 在完成每个write后,在文件表项中的当前文件偏移量即增加所写入的字节数。如果这导致当前文件偏移量超出了当前文件长度,则将i节点表项中的当前文件长度设置为当前文件偏移量(也就时是该文件加长了)。
  • 如果使用O_APPEND标志打开一个文件,则相应标志也被设置到文件表项的文件状态中。每次对这种具有追加写标志的文件执行写操作时,文件表项中的当前文件偏移量首先会被设置为i节点表项中的文件长度。这就使得每次写入的数据都追加到文件的当前尾端处。
  • 若一个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为i节点表项中的当前文件长度(注意,这与用O_APPEND标志打开文件是不同的。)。
  • lseek函数只修改文件表项中的当前文件偏移量,不进行任何I/O操作。
作者:myfather103 发表于2017/11/8 15:25:44 原文链接
阅读:21 评论:0 查看评论

洛谷P1962 斐波那契数列

$
0
0

矩阵乘法

题目传送门

求斐波那契数列第n项谁都会,但是这里n有long long ,O(n)推显然是不行的。于是我们就需要考虑使用矩阵快速幂优化。

构造一个矩阵[ f[n1]f[n] ]
再构造一个变换矩阵,使得[ f[n2]f[n1] ]×x=[ f[n1]f[n] ]

很显然x是2*2的矩阵。不妨设x=[acbd]

那么有如下等式:

[ f[n2]f[n1] ]×[acbd]=[ f[n1]f[n] ]

根据矩阵乘法,可以得到各项系数:
x=[0111]

根据矩乘的结合率,我们可以得到:

[ f[1]f[2] ]×[0111]n2=[ f[n1]f[n] ]

于是我们就可以用快速幂在O(log2n)的时间得到第n项了。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL MOD=1e9+7;
struct Matrix{
    int n,m;
    LL a[3][3];
}a,b;
LL n;
Matrix operator * (const Matrix &x,const Matrix &b){
    Matrix c;
    c.n=x.n,c.m=b.m;
    for (int i=1;i<=c.n;i++)
        for (int j=1;j<=c.m;j++){
            c.a[i][j]=0;
            for (int k=1;k<=x.m;k++)
                (c.a[i][j]+=x.a[i][k]*b.a[k][j])%=MOD;
        }
    return c;
} 
Matrix ksm(Matrix a,LL b){
    Matrix ret;
    ret.m=ret.n=2;
    for (int i=1;i<=ret.n;i++)
        for (int j=1;j<=ret.m;j++)
            ret.a[i][j]=1;
    while (b){
        if (b&1) ret=ret*a;
        b>>=1; a=a*a;
    }
    return ret;
}
int main(){
    scanf("%lld",&n);
    if (n==1) return printf("1\n"),0; 
    b.a[1][1]=0; b.a[1][2]=1;
    b.a[2][1]=1; b.a[2][2]=1;
    b.n=b.m=2;
    a.a[1][1]=0; a.a[1][2]=1;
    a.n=1,a.m=2;
    printf("%lld\n",(a*ksm(b,n-2)).a[1][2]);
    return 0;
} 
作者:a1799342217 发表于2017/11/8 15:44:26 原文链接
阅读:24 评论:0 查看评论

最简单的基于FFmpeg的AVDevice例子(屏幕录制)

$
0
0

原文地址:http://blog.csdn.net/leixiaohua1020/article/details/39706721 雷神的博客,致敬雷神。


抓屏方法

上篇文章记录了libavdevice的使用方法,本文不再重复。在Windows系统使用libavdevice抓取屏幕数据有两种方法:gdigrab和dshow。下文分别介绍。
1. gdigrab
gdigrab是FFmpeg专门用于抓取Windows桌面的设备。非常适合用于屏幕录制。它通过不同的输入URL支持两种方式的抓取:
(1)“desktop”:抓取整张桌面。或者抓取桌面中的一个特定的区域。
(2)“title={窗口名称}”:抓取屏幕中特定的一个窗口(目前中文窗口还有乱码问题)。
gdigrab另外还支持一些参数,用于设定抓屏的位置:
offset_x:抓屏起始点横坐标。
offset_y:抓屏起始点纵坐标。
video_size:抓屏的大小。
framerate:抓屏的帧率。
参考的代码如下:

  1. //Use gdigrab  
  2.  AVDictionary* options = NULL;  
  3.  //Set some options  
  4.  //grabbing frame rate  
  5.  //av_dict_set(&options,"framerate","5",0);  
  6.  //The distance from the left edge of the screen or desktop  
  7.  //av_dict_set(&options,"offset_x","20",0);  
  8.  //The distance from the top edge of the screen or desktop  
  9.  //av_dict_set(&options,"offset_y","40",0);  
  10.  //Video frame size. The default is to capture the full screen  
  11.  //av_dict_set(&options,"video_size","640x480",0);  
  12.  AVInputFormat *ifmt=av_find_input_format("gdigrab");  
  13.  if(avformat_open_input(&pFormatCtx,"desktop",ifmt,&options)!=0){  
  14.   printf("Couldn't open input stream.(无法打开输入流)\n");  
  15.   return -1;  
  16.   }  

2. dshow
使用dshow抓屏需要安装抓屏软件:screen-capture-recorder
软件地址:http://sourceforge.net/projects/screencapturer/
下载软件安装完成后,可以指定dshow的输入设备为“screen-capture-recorder”即可。有关dshow设备的使用方法在上一篇文章中已经有详细叙述,这里不再重复。参考的代码如下:
  1. AVInputFormat *ifmt=av_find_input_format("dshow");  
  2.  if(avformat_open_input(&pFormatCtx,"video=screen-capture-recorder",ifmt,NULL)!=0){  
  3.   printf("Couldn't open input stream.(无法打开输入流)\n");  
  4.   return -1;  
  5.  }  

注:上述两种抓屏方法也可以直接使用ffmpeg.exe的命令行完成,可以参考文章:

FFmpeg获取DirectShow设备数据(摄像头,录屏)

在Linux下可以使用x11grab抓屏,在MacOS下可以使用avfoundation抓屏,在这里不再详细叙述。

代码

下面直接贴上程序代码:

  1. /** 
  2.  * 最简单的基于FFmpeg的AVDevice例子(屏幕录制) 
  3.  * Simplest FFmpeg Device (Screen Capture) 
  4.  * 
  5.  * 雷霄骅 Lei Xiaohua 
  6.  * leixiaohua1020@126.com 
  7.  * 中国传媒大学/数字电视技术 
  8.  * Communication University of China / Digital TV Technology 
  9.  * http://blog.csdn.net/leixiaohua1020 
  10.  * 
  11.  * 本程序实现了屏幕录制功能。可以录制并播放桌面数据。是基于FFmpeg 
  12.  * 的libavdevice类库最简单的例子。通过该例子,可以学习FFmpeg中 
  13.  * libavdevice类库的使用方法。 
  14.  * 本程序在Windows下可以使用2种方式录制屏幕: 
  15.  *  1.gdigrab: Win32下的基于GDI的屏幕录制设备。 
  16.  *             抓取桌面的时候,输入URL为“desktop”。 
  17.  *  2.dshow: 使用Directshow。注意需要安装额外的软件screen-capture-recorder 
  18.  * 在Linux下可以使用x11grab录制屏幕。 
  19.  * 在MacOS下可以使用avfoundation录制屏幕。 
  20.  * 
  21.  * This software capture screen of computer. It's the simplest example 
  22.  * about usage of FFmpeg's libavdevice Library.  
  23.  * It's suiltable for the beginner of FFmpeg. 
  24.  * This software support 2 methods to capture screen in Microsoft Windows: 
  25.  *  1.gdigrab: Win32 GDI-based screen capture device. 
  26.  *             Input URL in avformat_open_input() is "desktop". 
  27.  *  2.dshow: Use Directshow. Need to install screen-capture-recorder. 
  28.  * It use x11grab to capture screen in Linux. 
  29.  * It use avfoundation to capture screen in MacOS. 
  30.  */  
  31.   
  32.   
  33. #include <stdio.h>  
  34.   
  35. #define __STDC_CONSTANT_MACROS  
  36.   
  37. #ifdef _WIN32  
  38. //Windows  
  39. extern "C"  
  40. {  
  41. #include "libavcodec/avcodec.h"  
  42. #include "libavformat/avformat.h"  
  43. #include "libswscale/swscale.h"  
  44. #include "libavdevice/avdevice.h"  
  45. #include "SDL/SDL.h"  
  46. };  
  47. #else  
  48. //Linux...  
  49. #ifdef __cplusplus  
  50. extern "C"  
  51. {  
  52. #endif  
  53. #include <libavcodec/avcodec.h>  
  54. #include <libavformat/avformat.h>  
  55. #include <libswscale/swscale.h>  
  56. #include <libavdevice/avdevice.h>  
  57. #include <SDL/SDL.h>  
  58. #ifdef __cplusplus  
  59. };  
  60. #endif  
  61. #endif  
  62.   
  63. //Output YUV420P   
  64. #define OUTPUT_YUV420P 0  
  65. //'1' Use Dshow   
  66. //'0' Use GDIgrab  
  67. #define USE_DSHOW 0  
  68.   
  69. //Refresh Event  
  70. #define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)  
  71.   
  72. #define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)  
  73.   
  74. int thread_exit=0;  
  75.   
  76. int sfp_refresh_thread(void *opaque)  
  77. {  
  78.     thread_exit=0;  
  79.     while (!thread_exit) {  
  80.         SDL_Event event;  
  81.         event.type = SFM_REFRESH_EVENT;  
  82.         SDL_PushEvent(&event);  
  83.         SDL_Delay(40);  
  84.     }  
  85.     thread_exit=0;  
  86.     //Break  
  87.     SDL_Event event;  
  88.     event.type = SFM_BREAK_EVENT;  
  89.     SDL_PushEvent(&event);  
  90.   
  91.     return 0;  
  92. }  
  93.   
  94. //Show Dshow Device  
  95. void show_dshow_device(){  
  96.     AVFormatContext *pFormatCtx = avformat_alloc_context();  
  97.     AVDictionary* options = NULL;  
  98.     av_dict_set(&options,"list_devices","true",0);  
  99.     AVInputFormat *iformat = av_find_input_format("dshow");  
  100.     printf("========Device Info=============\n");  
  101.     avformat_open_input(&pFormatCtx,"video=dummy",iformat,&options);  
  102.     printf("================================\n");  
  103. }  
  104.   
  105. //Show AVFoundation Device  
  106. void show_avfoundation_device(){  
  107.     AVFormatContext *pFormatCtx = avformat_alloc_context();  
  108.     AVDictionary* options = NULL;  
  109.     av_dict_set(&options,"list_devices","true",0);  
  110.     AVInputFormat *iformat = av_find_input_format("avfoundation");  
  111.     printf("==AVFoundation Device Info===\n");  
  112.     avformat_open_input(&pFormatCtx,"",iformat,&options);  
  113.     printf("=============================\n");  
  114. }  
  115.   
  116.   
  117.   
  118. int main(int argc, char* argv[])  
  119. {  
  120.   
  121.     AVFormatContext *pFormatCtx;  
  122.     int             i, videoindex;  
  123.     AVCodecContext  *pCodecCtx;  
  124.     AVCodec         *pCodec;  
  125.       
  126.     av_register_all();  
  127.     avformat_network_init();  
  128.     pFormatCtx = avformat_alloc_context();  
  129.       
  130.     //Open File  
  131.     //char filepath[]="src01_480x272_22.h265";  
  132.     //avformat_open_input(&pFormatCtx,filepath,NULL,NULL)  
  133.   
  134.     //Register Device  
  135.     avdevice_register_all();  
  136.     //Windows  
  137. #ifdef _WIN32  
  138. #if USE_DSHOW  
  139.     //Use dshow  
  140.     //  
  141.     //Need to Install screen-capture-recorder  
  142.     //screen-capture-recorder  
  143.     //Website: http://sourceforge.net/projects/screencapturer/  
  144.     //  
  145.     AVInputFormat *ifmt=av_find_input_format("dshow");  
  146.     if(avformat_open_input(&pFormatCtx,"video=screen-capture-recorder",ifmt,NULL)!=0){  
  147.         printf("Couldn't open input stream.\n");  
  148.         return -1;  
  149.     }  
  150. #else  
  151.     //Use gdigrab  
  152.     AVDictionary* options = NULL;  
  153.     //Set some options  
  154.     //grabbing frame rate  
  155.     //av_dict_set(&options,"framerate","5",0);  
  156.     //The distance from the left edge of the screen or desktop  
  157.     //av_dict_set(&options,"offset_x","20",0);  
  158.     //The distance from the top edge of the screen or desktop  
  159.     //av_dict_set(&options,"offset_y","40",0);  
  160.     //Video frame size. The default is to capture the full screen  
  161.     //av_dict_set(&options,"video_size","640x480",0);  
  162.     AVInputFormat *ifmt=av_find_input_format("gdigrab");  
  163.     if(avformat_open_input(&pFormatCtx,"desktop",ifmt,&options)!=0){  
  164.         printf("Couldn't open input stream.\n");  
  165.         return -1;  
  166.     }  
  167.   
  168. #endif  
  169. #elif defined linux  
  170.     //Linux  
  171.     AVDictionary* options = NULL;  
  172.     //Set some options  
  173.     //grabbing frame rate  
  174.     //av_dict_set(&options,"framerate","5",0);  
  175.     //Make the grabbed area follow the mouse  
  176.     //av_dict_set(&options,"follow_mouse","centered",0);  
  177.     //Video frame size. The default is to capture the full screen  
  178.     //av_dict_set(&options,"video_size","640x480",0);  
  179.     AVInputFormat *ifmt=av_find_input_format("x11grab");  
  180.     //Grab at position 10,20  
  181.     if(avformat_open_input(&pFormatCtx,":0.0+10,20",ifmt,&options)!=0){  
  182.         printf("Couldn't open input stream.\n");  
  183.         return -1;  
  184.     }  
  185. #else  
  186.     show_avfoundation_device();  
  187.     //Mac  
  188.     AVInputFormat *ifmt=av_find_input_format("avfoundation");  
  189.     //Avfoundation  
  190.     //[video]:[audio]  
  191.     if(avformat_open_input(&pFormatCtx,"1",ifmt,NULL)!=0){  
  192.         printf("Couldn't open input stream.\n");  
  193.         return -1;  
  194.     }  
  195. #endif  
  196.   
  197.     if(avformat_find_stream_info(pFormatCtx,NULL)<0)  
  198.     {  
  199.         printf("Couldn't find stream information.\n");  
  200.         return -1;  
  201.     }  
  202.     videoindex=-1;  
  203.     for(i=0; i<pFormatCtx->nb_streams; i++)   
  204.         if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)  
  205.         {  
  206.             videoindex=i;  
  207.             break;  
  208.         }  
  209.     if(videoindex==-1)  
  210.     {  
  211.         printf("Didn't find a video stream.\n");  
  212.         return -1;  
  213.     }  
  214.     pCodecCtx=pFormatCtx->streams[videoindex]->codec;  
  215.     pCodec=avcodec_find_decoder(pCodecCtx->codec_id);  
  216.     if(pCodec==NULL)  
  217.     {  
  218.         printf("Codec not found.\n");  
  219.         return -1;  
  220.     }  
  221.     if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)  
  222.     {  
  223.         printf("Could not open codec.\n");  
  224.         return -1;  
  225.     }  
  226.     AVFrame *pFrame,*pFrameYUV;  
  227.     pFrame=av_frame_alloc();  
  228.     pFrameYUV=av_frame_alloc();  
  229.     //unsigned char *out_buffer=(unsigned char *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));  
  230.     //avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);  
  231.     //SDL----------------------------  
  232.     if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {    
  233.         printf( "Could not initialize SDL - %s\n", SDL_GetError());   
  234.         return -1;  
  235.     }   
  236.     int screen_w=640,screen_h=360;  
  237.     const SDL_VideoInfo *vi = SDL_GetVideoInfo();  
  238.     //Half of the Desktop's width and height.  
  239.     screen_w = vi->current_w/2;  
  240.     screen_h = vi->current_h/2;  
  241.     SDL_Surface *screen;   
  242.     screen = SDL_SetVideoMode(screen_w, screen_h, 0,0);  
  243.   
  244.     if(!screen) {    
  245.         printf("SDL: could not set video mode - exiting:%s\n",SDL_GetError());    
  246.         return -1;  
  247.     }  
  248.     SDL_Overlay *bmp;   
  249.     bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,SDL_YV12_OVERLAY, screen);   
  250.     SDL_Rect rect;  
  251.     rect.x = 0;      
  252.     rect.y = 0;      
  253.     rect.w = screen_w;      
  254.     rect.h = screen_h;    
  255.     //SDL End------------------------  
  256.     int ret, got_picture;  
  257.   
  258.     AVPacket *packet=(AVPacket *)av_malloc(sizeof(AVPacket));  
  259.   
  260. #if OUTPUT_YUV420P   
  261.     FILE *fp_yuv=fopen("output.yuv","wb+");    
  262. #endif    
  263.   
  264.     struct SwsContext *img_convert_ctx;  
  265.     img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);   
  266.     //------------------------------  
  267.     SDL_Thread *video_tid = SDL_CreateThread(sfp_refresh_thread,NULL);  
  268.     //  
  269.     SDL_WM_SetCaption("Simplest FFmpeg Grab Desktop",NULL);  
  270.     //Event Loop  
  271.     SDL_Event event;  
  272.   
  273.     for (;;) {  
  274.         //Wait  
  275.         SDL_WaitEvent(&event);  
  276.         if(event.type==SFM_REFRESH_EVENT){  
  277.             //------------------------------  
  278.             if(av_read_frame(pFormatCtx, packet)>=0){  
  279.                 if(packet->stream_index==videoindex){  
  280.                     ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);  
  281.                     if(ret < 0){  
  282.                         printf("Decode Error.\n");  
  283.                         return -1;  
  284.                     }  
  285.                     if(got_picture){  
  286.                         SDL_LockYUVOverlay(bmp);  
  287.                         pFrameYUV->data[0]=bmp->pixels[0];  
  288.                         pFrameYUV->data[1]=bmp->pixels[2];  
  289.                         pFrameYUV->data[2]=bmp->pixels[1];       
  290.                         pFrameYUV->linesize[0]=bmp->pitches[0];  
  291.                         pFrameYUV->linesize[1]=bmp->pitches[2];     
  292.                         pFrameYUV->linesize[2]=bmp->pitches[1];  
  293.                         sws_scale(img_convert_ctx, (const unsigned charconst*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);  
  294.   
  295. #if OUTPUT_YUV420P    
  296.                         int y_size=pCodecCtx->width*pCodecCtx->height;      
  297.                         fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);    //Y     
  298.                         fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //U    
  299.                         fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //V    
  300. #endif    
  301.                         SDL_UnlockYUVOverlay(bmp);   
  302.                           
  303.                         SDL_DisplayYUVOverlay(bmp, &rect);   
  304.   
  305.                     }  
  306.                 }  
  307.                 av_free_packet(packet);  
  308.             }else{  
  309.                 //Exit Thread  
  310.                 thread_exit=1;  
  311.             }  
  312.         }else if(event.type==SDL_QUIT){  
  313.             thread_exit=1;  
  314.         }else if(event.type==SFM_BREAK_EVENT){  
  315.             break;  
  316.         }  
  317.   
  318.     }  
  319.   
  320.   
  321.     sws_freeContext(img_convert_ctx);  
  322.   
  323. #if OUTPUT_YUV420P   
  324.     fclose(fp_yuv);  
  325. #endif   
  326.   
  327.     SDL_Quit();  
  328.   
  329.     //av_free(out_buffer);  
  330.     av_free(pFrameYUV);  
  331.     avcodec_close(pCodecCtx);  
  332.     avformat_close_input(&pFormatCtx);  
  333.   
  334.     return 0;  
  335. }  





结果

程序的运行效果如下。这个运行结果还是十分有趣的,会出现一个屏幕“嵌套”在另一个屏幕里面的现象,环环相套。
 
可以通过代码定义的宏来确定是否将解码后的YUV420P数据输出成文件:
  1. #define OUTPUT_YUV420P 0  

可以通过下面的宏定义来确定使用GDIGrab或者是Dshow打开摄像头:

  1. //'1' Use Dshow   
  2. //'0' Use GDIgrab  
  3. #define USE_DSHOW 0  


下载


Simplest FFmpeg Device 


项目主页

SourceForge:https://sourceforge.net/projects/simplestffmpegdevice/

Github:https://github.com/leixiaohua1020/simplest_ffmpeg_device

开源中国:http://git.oschina.net/leixiaohua1020/simplest_ffmpeg_device


CSDN下载地址:
http://download.csdn.net/detail/leixiaohua1020/7994049

注:
 本工程包含两个基于FFmpeg的libavdevice的例子:
 simplest_ffmpeg_grabdesktop:屏幕录制。
 simplest_ffmpeg_readcamera:读取摄像头


更新-1.1(2015.1.9)=========================================

该版本中,修改了SDL的显示方式,弹出的窗口可以移动了。

CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8344695


更新-1.2 (2015.2.13)=========================================

这次考虑到了跨平台的要求,调整了源代码。经过这次调整之后,源代码可以在以下平台编译通过:

VC++:打开sln文件即可编译,无需配置。

cl.exe:打开compile_cl.bat即可命令行下使用cl.exe进行编译,注意可能需要按照VC的安装路径调整脚本里面的参数。编译命令如下。

[plain] view plain copy
  1. ::VS2010 Environment  
  2. call "D:\Program Files\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"  
  3. ::include  
  4. @set INCLUDE=include;%INCLUDE%  
  5. ::lib  
  6. @set LIB=lib;%LIB%  
  7. ::compile and link  
  8. cl simplest_ffmpeg_grabdesktop.cpp /MD /link SDL.lib SDLmain.lib avcodec.lib ^  
  9. avformat.lib avutil.lib avdevice.lib avfilter.lib postproc.lib swresample.lib swscale.lib ^  
  10. /SUBSYSTEM:WINDOWS /OPT:NOREF  

MinGW:MinGW命令行下运行compile_mingw.sh即可使用MinGW的g++进行编译。编译命令如下。

[plain] view plain copy
  1. g++ simplest_ffmpeg_grabdesktop.cpp -g -o simplest_ffmpeg_grabdesktop.exe \  
  2. -I /usr/local/include -L /usr/local/lib \  
  3. -lmingw32 -lSDLmain -lSDL -lavformat -lavcodec -lavutil -lavdevice -lswscale  

GCC(Linux):Linux命令行下运行compile_gcc.sh即可使用GCC进行编译。编译命令如下。

[plain] view plain copy
  1. gcc simplest_ffmpeg_grabdesktop.cpp -g -o simplest_ffmpeg_grabdesktop.out \  
  2. -I /usr/local/include -L /usr/local/lib -lSDLmain -lSDL -lavformat -lavcodec -lavutil -lavdevice -lswscale  

GCC(MacOS):MacOS命令行下运行compile_gcc_mac.sh即可使用GCC进行编译。Mac的GCC和Linux的GCC差别不大,但是使用SDL1.2的时候,必须加上“-framework Cocoa”参数,否则编译无法通过。编译命令如下。

[plain] view plain copy
  1. gcc simplest_ffmpeg_grabdesktop.cpp -g -o simplest_ffmpeg_grabdesktop.out \  
  2. -framework Cocoa -I /usr/local/include -L /usr/local/lib -lSDLmain -lSDL -lavformat -lavcodec -lavutil -lavdevice -lswscale  

PS:相关的编译命令已经保存到了工程文件夹中


CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8445747

SourceForge上已经更新。


如有错误请指正:

交流请加QQ群:62054820
QQ:379969650.




作者:zhuweigangzwg 发表于2017/11/8 15:51:10 原文链接
阅读:26 评论:0 查看评论

第5章 TCP客户/服务器程序示例

$
0
0

客户端:

#include "../Gnet.h"

void do_client(int connfd)
{
    char buf[MAX_LINE];

    while(fgets(buf, MAX_LINE, stdin) != NULL)
    {
        Write(connfd, buf, strlen(buf));
        memset(buf, 0, MAX_LINE);
        if(Read(connfd, buf, MAX_LINE) == 0)
            perr_exit("server terminated.\n");
        fputs(buf, stdout);
    }
}

int main(int argc, const char* argv[])
{
    int connfd;
    struct sockaddr_in server_addr;

    if(argc < 2)
        perr_exit("usage : client <IPaddress>");

    connfd = Socket(AF_INET, SOCK_STREAM, 0);
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
    Connect(connfd, (struct sockaddr*)&server_addr, sizeof(server_addr));

    do_client(connfd);

    return 0;
}

服务器:

#include "../Gnet.h"

void do_server(int connfd)
{
    ssize_t nread;
    char buf[MAX_LINE];

    while((nread = Read(connfd, buf, MAX_LINE)) > 0)
        Write(connfd, buf, nread);
}

void sig_child(int signo)
{
    pid_t pid;
    int stat;

    printf("in sig_child.\n");
    while((pid = waitpid(-1,&stat, WNOHANG)) > 0)
        printf("child %d terminated\n", pid);
    printf("out sig_child.\n");
}

int main(int argc, const char* argv[])
{
    int lfd, connfd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len;
    pid_t child_id;

    struct sigaction sigaction_set, sigaction_get;
    sigaction_set.sa_handler = sig_child;
    sigemptyset(&sigaction_set.sa_mask);
    sigaction_set.sa_flags = 0;
    if(sigaction(SIGCHLD, &sigaction_set, &sigaction_get) <0)
        printf("sigaction(SIGCHLD) error!\n");

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERVER_PORT);

    lfd = Socket(AF_INET, SOCK_STREAM, 0);
    Bind(lfd, (const struct sockaddr*)&server_addr, sizeof(server_addr));
    Listen(lfd, LISTENQ);
    printf("waiting for connecting.\n");

    memset(&client_addr, 0, sizeof(client_addr));
    while(1)
    {
        client_addr_len = sizeof(client_addr_len);
        connfd = Accept(lfd, (struct sockaddr*)&client_addr, &client_addr_len);

        if((child_id = fork()) == 0)//子进程
        {
            Close(lfd);
            do_server(connfd);
            Close(connfd);
            exit(0);
        }
        else//父进程
        {
            printf("child %d connected\n", child_id);
            Close(connfd);
        }
    }

    return 0;
}

对基本的网络函数做了封装:

Gnet.h

#ifndef __GNET_H__

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <errno.h>
#include <signal.h>
#include <wait.h>

#define SERVER_PORT (49152)
#define LISTENQ (5)
#define MAX_LINE (256)

void perr_exit(const char* str);
int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr);
int Bind(int fd, const struct sockaddr* sa, socklen_t salen);
int Connect(int fd, const struct sockaddr* sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void* ptr, size_t nbytes);
ssize_t Write(int fd, const void* ptr, size_t nbytes);
int Close(int fd);

ssize_t Readn(int fd, void* vptr, size_t n);
ssize_t Writen(int fd, const void* vptr, size_t n);
//ssize_t my_read(int fd, char* ptr);
ssize_t Readline(int fd, void* vptr, size_t maxlen);

#endif

Gnet.c

#include "Gnet.h"

void perr_exit(const char* str)
{
    perror(str);
    exit(-1);
}

int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr)
{
   int afd;

AGAIN:
   if((afd = accept(fd, sa, salenptr)) <0 )
   {
       if((errno == ECONNABORTED) || (errno == EINTR))
           goto AGAIN;
       else
           perr_exit("accept error");
   }

   return afd;
}

int Bind(int fd, const struct sockaddr* sa, socklen_t salen)
{
    int bfd;

    if((bfd = bind(fd, sa, salen)) < 0)
        perr_exit("bind error");

    return bfd;
}

int Connect(int fd, const struct sockaddr* sa, socklen_t salen)
{
    int cfd;

    if((cfd = connect(fd, sa, salen)) < 0)
        perr_exit("connect error");

    return cfd;
}

int Listen(int fd, int backlog)
{
    int res;

    if((res = listen(fd, backlog)) < 0)
        perr_exit("listen error");

    return res;
}

int Socket(int family, int type, int protocol)
{
    int sfd;

    if((sfd = socket(family, type, protocol)) < 0)
        perr_exit("socket error");

    return sfd;
}

ssize_t Read(int fd, void* ptr, size_t nbytes)
{
    ssize_t n;

AGAIN:
    if((n = read(fd, ptr, nbytes)) == -1)
    {
        if(errno == EINTR)
            goto AGAIN;
        else
            return -1;
    }

    return n;
}

ssize_t Write(int fd, const void* ptr, size_t nbytes)
{
    ssize_t n;

AGAIN:
    if((n = write(fd, ptr, nbytes)) == -1)
    {
        if(errno == EINTR)
            goto AGAIN;
        else
            return -1;
    }

    return n;
}

int Close(int fd)
{
    int res;

    if((res = close(fd)) == -1)
        perr_exit("close error");

    return res;
}

ssize_t Readn(int fd, void* vptr, size_t n)
{
    ssize_t nleft;
    ssize_t nread;
    char* ptr;

    ptr = (char*)vptr;
    nleft = n;

    while(nleft > 0)
    {
        if((nread = read(fd, ptr, nleft)) < 0)
        {
            if(errno == EINTR)
                nread = 0;
            else
                return -1;
        }
        else if(nread == 0)
            break;

        nleft -= nread;
        ptr += nread;
    }       

    return n-nleft;
}

ssize_t Writen(int fd, const void* vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char* ptr;

    ptr = (const char*)vptr;
    nleft = n;

    while(nleft > 0)
    {
        if((nwritten = write(fd, ptr, nleft)) <= 0)
        {
            if(nwritten < 0 && errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }

        nleft -= nwritten;
        ptr += nwritten;
    }

    return n - nleft;
}

static ssize_t my_read(int fd, char* ptr)
{
    static int read_cnt;
    static char* read_ptr;
    static char read_buf[MAX_LINE];

    if(read_cnt <= 0)
    {
AGAIN:
        if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0)
        {
            if(errno == EINTR)
                goto AGAIN;
            else
                return -1;
        }
        else if(read_cnt == 0)
            return 0;

        read_ptr = read_buf;
    }

    --read_cnt;
    *ptr = *read_ptr++;

    return 1;
}

ssize_t Readline(int fd, void* vptr, size_t maxlen)
{
    ssize_t n, rc;
    char c, *ptr;

    ptr = (char*)vptr;

    for(n = 1; (size_t)n < maxlen; ++n)
    {
        if((rc = my_read(fd, &c)) == 1)
        {
            *ptr++ = c;
            if(c == '\n')
                break;
        }
        else if(rc == 0)
        {
            *ptr = '\0';
            return n-1;
        }
        else
            return -1;
    }

    *ptr = '\0';

    return n;
}

从今以后的学习代码都会上传到github:https://github.com/gongluck/unp-notes.git
作者:gongluck93 发表于2017/11/8 15:54:32 原文链接
阅读:33 评论:0 查看评论

【蓝桥杯】【密码发生器】

$
0
0

题目
在对银行账户等重要权限设置密码的时候,我们常常遇到这样的烦恼:如果为了好记用生日吧,容易被破解,不安全;
如果设置不好记的密码,又担心自己也会忘记;如果写在纸上,担心纸张被别人发现或弄丢了…
这个程序的任务就是把一串拼音字母转换为6位数字(密码)。
我们可以使用任何好记的拼音串(比如名字,王喜明,就写:wangximing)作为输入,程序输出6位数字。
变换的过程如下:
第一步. 把字符串6个一组折叠起来,比如wangximing则变为:
wangxi
ming
第二步. 把所有垂直在同一个位置的字符的ascii码值相加,得出6个数字,如上面的例子,则得出:
228 202 220 206 120 105
第三步. 再把每个数字“缩位”处理:就是把每个位的数字相加,得出的数字如果不是一位数字,就再缩位,
直到变成一位数字为止。例如: 228 => 2+2+8=12 => 1+2=3
上面的数字缩位后变为:344836, 这就是程序最终的输出结果!
要求程序从标准输入接收数据,在标准输出上输出结果。
输入格式为:第一行是一个整数n(<100),表示下边有多少输入行,接下来是n行字符串,就是等待变换的字符串。
输出格式为:n行变换后的6位密码。

例如,输入:
5
zhangfeng
wangximing
jiujingfazi
woaibeijingtiananmen
haohaoxuexi

则输出:
772243
344836
297332
716652
875843

注意:
请仔细调试!您的程序只有能运行出正确结果的时候才有机会得分!
在评卷时使用的输入数据与试卷中给出的实例数据可能是不同的。
请把所有类写在同一个文件中,调试好后,存入与【考生文件夹】下对应题号的“解答.txt”中即可。
相关的工程文件不要拷入。
请不要使用package语句。
源程序中只能出现JDK1.5中允许的语法或调用。不能使用1.6或更高版本。

分析
解题需要耐心、信心。
基本思想是将问题分解,大的问题分解成小的问题。
设计两个函数,一个负责计算字符串得出6个数字,一个负责缩位。

源码

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        String[] ss = new String[n];
        for (int i = 0; i < n; i++) {
            ss[i] = sc.next();
        }
        sc.close();


        for (String s : ss) {
            System.out.println(f1(s));
        }

    }

    private static String f1(String s) {
        //确定字符数组的行数r
        int r = 0;
        if(s.length()%6==0) {
            r = s.length()/6;
        }else {
            r = s.length()/6+1;
        }

        //为字符数组a赋值
        char[][] a = new char[r][6];
        int index = 0;
        ok:for (int i = 0; i < r; i++) {
            for (int j = 0; j < 6; j++) {
                if(index == s.length()) {
                    break ok;
                }
                a[i][j] = s.charAt(index++);
            }

        }

        //计算每列的元素之和
        int[] b = new int[6];
        for(int j=0; j<6; j++) {
            int sum = 0;
            for (int i = 0; i < r; i++) {
                sum += a[i][j];
            }
            b[j] = sum;
        }

        StringBuilder sb = new StringBuilder();
        //将每个数字进行缩位处理
        for (int i = 0; i < b.length; i++) {
            b[i] = f2(b[i]);
            sb.append(b[i]);
        }

        return sb.toString();

    }

    //缩位处理
    private static int f2(int x) {
        if(x>=0&&x<10) {
            return x;
        }
        int sum = 0;
        String s = String.valueOf(x);
        for (int i = 0; i < s.length(); i++) {
            sum += (s.charAt(i)-'0');
        }
        return f2(sum);

    }
作者:bear_huangzhen 发表于2017/11/8 16:10:08 原文链接
阅读:35 评论:0 查看评论

一起学习Kotlin——Kotlin中的基本数据类型

$
0
0

Kotlin中的基本数据类型

作为Google【官方指定的干儿子】,Kotlin语言今年受到了越来越多的重视。无论是开发Android还是后台程序,这门语言以后一定是大有可为。由于相关的文章很多,那我们不多介绍这门语言是怎么来的。就让我们慢慢来,一点一点的去品味Kotlin。

第一章,一起来看看这门语言的基本数据类型吧。


开始之前,我们需要明确一点:

kotlin是一门【强类型】、【静态类型】、【支持隐式类型】的【显式】类型语言。

PS:
1、强类型:强类型语言的编译器引入了较多的类型检查限制,因此在运行时不会发生未经明确(显示转换)类型转换的类型转换。常见的语言中,Java就是强类型语言,而JavaScript则是弱类型语言。
2、静态类型:根据类型检查在编译时期还是运行时期,我们把语言分为静态类型以及动态类型。静态语言是基于编译器来确保语言类型的安全。
3、显式类型:根据变量名是否需要显式的给出声明,我们可以将语言分为显式类型以及隐式类型。kotlin是显式类型的语言,与此同时又因为有【类型推断】的作用所以可以看做是支持隐式类型。


一、简述

1、var 与 val

kotlin中,var符号表示的事变量,可以多次重复赋值。
而val表示的是“常量”,一次赋值之后,不能再次修改。


2、kotlin中的根类型:Any

kotlin中万物皆对象,所有类型都是引用类型。所有的类都有一个超类:Any。
官方文档是这么说的:

/**
* The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
*/

这个类只有三个方法:

public open operator fun equals(other: Any?): Boolean
public open fun hashCode(): Int
public open fun toString(): String

如果类声明中,没有指明超类,则默认为Any。
kotlin中的Any映射为java中的java.lang.Object.

特别注意的一点:java中Object只是所有引用类型的超类,而基本类型int、long等不包含在内。而kotlin中,Any是一切的超类。


3、kotlin中的数字类型

3.1、常识介绍

kotlin中常见的数字类型分为以下6种:

方法名 转换类型 补充
Byte 8位 1字节
Short 16位 2字节
Int 32位 4字节
Long 64位 8字节
Float 32位 4字节
Double 64位 8字节

根据名称可以看出基本和java中的相关定义相近。
kotlin支持二进制、十进制、十六进制;

但【不】支持八进制

与此同时,kotlin在数值类型的表示方式上也支持下划线,如:
val Million = 1000_000_000
val tmp = 123_456_789L
val demo = 0xDD_FF_EE

kotlin中的数字类型与java很是相近,但有一点不同:

kotlin没有java语言的隐式变换(如byte->short->int->long 等)。

因此,java中的如下代码,在kotlin中是无法编译的:

short a = 1;
int b = a;

当然,在kotlin中应该这样描述:

val a : Int = 1
val b : Long = a

上述代码同样是无法通过编译的!

因此,遇到类型转换我们就应该考虑一下【显式转换】:
java中,我们一般会这样进行数值类型的显式转换:

int a = 1;
...
Long tmp = (long)a;

而kotlin中,我们可以这样:

val a : Int = 1
val b : a.toLong()

相关的方法汇总如下:

方法名 转换类型
toByte() Byte
toShort() Short
toInt() Int
toLong() Long
toFloat() Float
toDouble() Double
toChar() Char

这里需要注意一点的是:

与java不同,kotlin中Char类型仅仅表示字符,不能再被直接当做数字。 因此,Char类型的变量必须在单引号之间表示:’变量’。(CSDN博客这里显示有误。。。)

一段简单的java代码如下:

char a = 'c';
System.out.println(a+1);

此时打印的结果为【数字】100。
而作为对比,在kotlin中:

val a : Char = 'c'
println(a+1)

上述代码的打印结果为【字符】d

因此我们要明确:Kotlin中 Char类型由于不是直接当数值用,所以最后返回依旧是Char型。


3.2、一些需要注意的点

3.2.1、== 与 ===

kotlin中并没有单纯的数值,在我们定义每一个数值的时候,kotlin编译器都在默默的为我们将定义的数值封装成一个对象,以最大限度的解决空指针异常的问题。因此kotlin中数值的比较除了我们常见的 == 号之外还多了 === 。
当然,根据面向对象的定义也可以快速的了解:

符号名 作用 否定形式
== 【结构相等】比较两个具体的【数值的大小】是否相同 !=
=== 【引用相等】比较两个对象【在内存的存储地址】是否相同 !==

ps:
编译器中,== 等同于equal()方法。

举个栗子来看:

fun main(args: Array<String>){
    val a : Int = 10
    println(a === a) //此处返回的值为true
    val b : Int = a
    println(a === b) //此处返回的值为true,因为赋值操作是内存地址赋值,而内存地址相同,其中的数字值自然相同
    println(a == b) //此处返回的值为true,原因同上

}

【☆☆☆】考虑到装箱问题:数字装箱不保留【同一性】,但保留数值的【相等性】。

    val a: Int = 200
    println(a === a) //此处返回的值为true
    val b :Int ? = a
    println(a === b) //此处返回的值为false,因为这是两个对象,内存地址不相同
    println(a == b) //此处返回的值为true,数值相同

当然还有一个特殊情况,考虑到JVM虚拟机的常量池特性:

val a: Int = 20
    println(a === a) //此处返回的值为true
    val b :Int ? = 20
    println(a === b) //此处返回的值为true
    println(a == b) //此处返回的值为true,数值相同

PS:
【1】JVM虚拟机中维护着有符号整形常量池(-128,127),在这个范围里的数值都会直接使用常量池的内存地址,所以这个范围内的数值装箱后比较内存地址依旧是相等的。(常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References))
【2】kotlin中,?表示这个对象可能为空。因此这一句“val b :Int ? = a”
表示的意思除了普通的数字赋值外,还多了一层含义:如果被赋值对象为空,则开辟内存创建新的对象。

3.2.2、移位操作

在java中,移位操作的表示非常简洁,直接用符号即可表示:

符号名称 作用
<< 左移位操作
*>> 右移位操作(此处*为显示效果)
*>>> 无符号右移位

而在kotlin中,则没有这些简单的符号表示,取而代之的是相应的移位操作方法:

符号名称 作用
shl(bits) 左移位
shr(bits) 右移位
ushr(bits) 无符号右移位
and(bits) 与操作
or(bits) 或操作
xor(bits) 异或操作
inv() 反向操作
3.2.3、运算符 + 的重载

先来看一段代码:

val a = 10L + 1

很明显,上述代码中,+ 左右两个数分别为Long类型与Int类型。
那么问题来了,我们前面说过,kotlin中并不支持数字类型的隐式转换,而且上面表达式中的数字1并没有显示拓宽为Long类型,为何却准确无误呢???
答案很简答,那就是kotlin中对于符号“+”的重载。
符号重载

看一看到,源码中有很多重载的方法。
当我们执行:val a = 10L + 1
实质上是:
val a = 10L.plus(1)

值得注意的一点是:XX.plus()方法中传入的形参只能为数值类型!


4、kotlin中的字符类型

kotlin中的基本字符为Char类型,相关的知识点我们已经在前文讨论过。


5、kotlin中的布尔类型

布尔用 Boolean 类型表示,它有两个值:true 和 false。
若需要可空引用布尔会被装箱。


6、kotlin中的字符串类型

kotlin中字符串是String类型。
与java类似,kotlin中的String同样是由final修饰的,即不可被继承。我们一起来看一些新的特性。

6.1、支持多行字符串

val string = """
            line 1
            line 2
            lin3 4
            """

注意以上代码的打印结果是存在前置的空格号的:
打印结果1

我们可以借助与 trimMargin() 发放做到去除掉每行的前置空格。

val string = """
            |line 1
            |line 2
            |lin3 4
            """.trimMargin("|")

这里有一点需要注意:trimMargin()方法需要传入一个String类型的参数作为删除前置空格并进行每行分割的标志Flag,我们在这里选用了”|”。

6.2、支持字符串模板

在kotlin中,字符串可以包含字符串模板用来表示前面用的到的一段代码。kotlin中规定,所有的字符串模板必须以美元符号”$”开头。

 val a = "temp"
 val q = "$a.length():"
 println(q+a.length)

打印结果为:
打印结果2

原生字符串和转义字符串内部都支持模板。

6.3、索引运算符s[i]

当我们需要对一个字符串的某一位取值时,java中会用到:

"abc".charAt(i);

而在kotlin中,我们可以简单的用索引运算符即可:

val s = "abc"
s[0]//结果为a

6.4、for循环迭代字符串

    for (s in "12345"){println(s)}

打印结果:
打印结果3


7、kotlin中的数组类型

7.1、生成数组

kotlin中的数组不是new出来的而是用了别的表示方法:
【1】fun_1

    val a = arrayOf(1,2,3) //1,2,3

【2】fun_2 工厂函数

    val b = Array(3, { i -> (i * 2) })//0,2,4

7.2、数组的多类型表示

kotlin中的数组支持多类型:

    val arr = arrayOf(1,2,true,"abc")
    for(c in arr){println(c)}

上述代码的打印结果为:
打印结果12
注意:在这里kotlin编译器自动将数组升级为java.lang.Object类型了。
打印结果123

7.3、新建空数组

除此之外在新建空数组时,我们必须显示地知名数组元素的类型:

    val b = arrayOfNulls(10)//错误
    val b = arrayOfNulls<Int>(10)//正确

7.4、数组转换

注意:kotlin中数组不是形变的,即我们不能将Array赋值给Array。


作者:xiaoyaozaimz 发表于2017/11/8 16:12:41 原文链接
阅读:51 评论:0 查看评论

5 分钟带你弄懂 k-means 聚类

$
0
0

聚类与分类的区别

分类:类别是已知的,通过对已知分类的数据进行训练和学习,找到这些不同类的特征,再对未分类的数据进行分类。属于监督学习。

聚类:事先不知道数据会分为几类,通过聚类分析将数据聚合成几个群体。聚类不需要对数据进行训练和学习。属于无监督学习。

关于监督学习和无监督学习,这里给一个简单的介绍:是否有监督,就看输入数据是否有标签,输入数据有标签,则为有监督学习,否则为无监督学习。更详尽的解释会在后续博文更新,这里不细说。

k-means 聚类

聚类算法有很多种,K-Means 是聚类算法中的最常用的一种,算法最大的特点是简单,好理解,运算速度快,但是只能应用于连续型的数据,并且一定要在聚类前需要手工指定要分成几类。

K-Means 聚类算法的大致意思就是“物以类聚,人以群分”:

  1. 首先输入 k 的值,即我们指定希望通过聚类得到 k 个分组;
  2. 从数据集中随机选取 k 个数据点作为初始大佬(质心);
  3. 对集合中每一个小弟,计算与每一个大佬的距离,离哪个大佬距离近,就跟定哪个大佬。
  4. 这时每一个大佬手下都聚集了一票小弟,这时候召开选举大会,每一群选出新的大佬(即通过算法选出新的质心)。
  5. 如果新大佬和老大佬之间的距离小于某一个设置的阈值(表示重新计算的质心的位置变化不大,趋于稳定,或者说收敛),可以认为我们进行的聚类已经达到期望的结果,算法终止。
  6. 如果新大佬和老大佬距离变化很大,需要迭代3~5步骤。

说了这么多,估计还是有点糊涂,下面举个非常形象简单的例子:
有6个点,从图上看应该可以分成两堆,前三个点一堆,后三个点另一堆。现在我手工地把 k-means 计算过程演示一下,同时检验是不是和预期一致:
这里写图片描述

1.设定 k 值为2

2.选择初始大佬(就选 P1 和 P2)

3.计算小弟与大佬的距离:

这里写图片描述
从上图可以看出,所有的小弟都离 P2 更近,所以次站队的结果是:

A 组:P1
B 组:P2、P3、P4、P5、P6

4.召开选举大会:

A 组没什么可选的,大佬就是自己
B 组有5个人,需要重新选大佬,这里要注意选大佬的方法是每个人 X 坐标的平均值和 Y 坐标的平均值组成的新的点,为新大佬,也就是说这个大佬是“虚拟的”。因此,B 组选出新大哥的坐标为:P 哥((1+3+8+9+10)/5,(2+1+8+10+7)/5)=(6.2,5.6)。
综合两组,新大哥为 P1(0,0),P哥(6.2,5.6),而P2-P6重新成为小弟。

5.再次计算小弟到大佬的距离:
这里写图片描述

这时可以看到P2、P3离P1更近,P4、P5、P6离P哥更近,所以第二次站队的结果是:

A 组:P1、P2、P3
B 组:P4、P5、P6(虚拟大哥这时候消失)

6.第二届选举大会:
同样的方法选出新的虚拟大佬:P哥1(1.33,1),P哥2(9,8.33),P1-P6都成为小弟。

7.第三次计算小弟到大佬的距离:
这里写图片描述
这时可以看到 P1、P2、P3 离 P哥1 更近,P4、P5、P6离 P哥2 更近,所以第二次站队的结果是:
A 组:P1、P2、P3
B 组:P4、P5、P6

我们可以发现,这次站队的结果和上次没有任何变化了,说明已经收敛,聚类结束,聚类结果和我们最开始设想的结果完全一致。

K-Means 聚类 MATLAB 实现

关于 K-Means 的算法具体代码,网上有各种版本,这里也不赘述了,下面结合 MATLAB 中的一些函数给出一个较为简洁的版本:

X2 = zscore(X);      % zscore方法标准化数据  
Y2 = pdist(X2);      % 计算距离(默认欧式距离)
Z2 = linkage(Y2);    % 定义变量之间的连接,用指定的算法计算系统聚类树
T = cluster(Z2,6);   % 创建聚类
H = dendrogram(Z2);  %作出系谱图

最终聚类系谱图如下所示:
这里写图片描述

当然,MATLAB 也提供了 kmeans() 函数可供直接聚类使用,详情可参与其文档。

作者:huangfei711 发表于2017/11/8 16:16:23 原文链接
阅读:30 评论:0 查看评论

0基础lua学习(十六)lua的多态 base

$
0
0

多态base

使用lua实现C++类似的多态,看起来略微难了一些,这个demo,默认提供了 init类似构造函数的时机。

--base.lua代码
--保存类类型的虚表
local _class = {}
function BaseClass(super)
    -- 生成一个类类型
    local class_type = {}
    -- 在创建对象的时候自动调用
    class_type.__init = false
    class_type.__delete = false
    class_type.super = super
    class_type.New = function(...)

        -- 生成一个类对象
        local obj = {}
        obj._class_type = class_type

        -- 在初始化之前注册基类方法
        setmetatable(obj, { __index = _class[class_type] })

        -- 调用初始化方法
        do
            local create
            create = function(c, ...)
                if c.super then
                    create(c.super, ...)
                end
                if c.__init then
                    c.__init(obj, ...)
                end
            end

            create(class_type, ...)
        end

        -- 注册一个delete方法
        obj.DeleteMe = function(self)
            local now_super = self._class_type
            while now_super ~= nil do
                if now_super.__delete then
                    now_super.__delete(self)
                end
                now_super = now_super.super
            end
        end

        return obj
    end

    local vtbl = {}
    _class[class_type] = vtbl

    setmetatable(class_type, {__newindex =
    --table key value
        function(t,k,v)
            vtbl[k] = v
        end
        ,
        __index = vtbl, --For call parent method
    })

    if super then
        setmetatable(vtbl, {__index =
            function(t,k)
                local ret = _class[super][k]

                return ret
            end
        })
    end

    return class_type
end
--main.lua代码
require"base"

father = father or BaseClass()
function father:__init()
    print("father:init")
end

function father:Bind()
    print("father:Bind")
end

function father:play()
    print("father:play")
end


son = son or BaseClass(father)

function son:__init()
    print("son:init")
end

function son:Bind()
    print("son:Bind")
end

function son:UnBind()

end


a = nil
a = son:New()
a:play()
a:Bind()

console:
father:init
son:init
father:play
son:Bind

多态base解析

1.首先要明确
father = father or BaseClass( )和 son = son or BaseClass(father)
有一个共同的表local _class = {},也就是虚表。

2.father = father or BaseClass(),我们会保存__class[class_type] = vtbl
classtype是key,value是表
返回的表是 class_type

                vtbl
                 {__newindex =
                    function(t,k,v)   
                        vtbl[k] = v
                    end,
                  __index = vtbl
                 }

3.function father:__init(),会将class_type表中的__init 从布尔,变为函数
4.function father:Bind(),没有Bind函数,所以会调用vtbl表中的__newindex,因为我们没有Bind的key

    function(t,k,v)   
        vtbl[k] = v
    end

这样vtbl中添加了一个Bind的key,value是函数
5.son = son or BaseClass(father)同理,但是它的vtbl会添加__index = function(t,k)的函数

6.function son:__init(),会将class_type表中的__init 从布尔,变为函数
7.function son:Bind(),所以会调用

    function(t,k,v)   
        vtbl[k] = v
    end

向son的vtbl表中加入函数
8.a = son:New()这是
son = son or BaseClass(father)里面的new方法,调用父类的init,然后调用子类的init,然后返回
_class[class_type],这个是son的vtbl表。(_class里面有两个vtbl,key分别是father的class_type和son的class_type,其值是各自的vtbl表)

9.a:play()这是调用父类的play方法,首先去son的vtbl表中查找,通过__index调用了

function(t,k)
 local ret = _class[super][k]
 return ret
end

_class[super]这是访问虚表中的,father的vtbl,然后我们找到了,然后进行调用。
注意:如果我们换成a:play1(),就会报错all method ‘play1’ (a nil value) ,也就是你声明函数,要加到父类的表里
也就是下面这步

function father:play()
    print("father:play")
end

10.a:Bind(),其实就是找son的vtbl,直接找到了函数,然后调用。

小结:

  • 我们声明的 son继承 father之前,需要创建两个表,每个表里都有一个vtbl , 有一个总表_class来管理
  • __newindex 实现的是,我们创建函数添加函数,加到vtbl 表里
  • __index 实现的是,继承的实现。即访问父类里面的函数,如果子类实现了,就访问子类的。
  • t 是table、k是key、v是value

看着是有点乱,但是你多看几遍,你就会有所收获。
这个代码可以说,我们在开发游戏的时候,使用的base代码。

作者:hiwoshixiaoyu 发表于2017/11/8 16:31:06 原文链接
阅读:30 评论:0 查看评论

NEW BLOG!

$
0
0

可能NOIP2017后就会把博客的重心转移了

当然CSDN也会同步更新,毕竟为了访问量qwq
新博客地址

http://www.yjjr.org/

很酷炫的名字和画面

作者:qwerty1125 发表于2017/11/8 17:52:28 原文链接
阅读:43 评论:0 查看评论

Vue 脱坑记 - 查漏补缺(汇总下群里高频询问的xxx及给出不靠谱的解决方案)

$
0
0

前言

发现群里有些问题的提问重复率太高了,每次都去回答,回答的贼烦.
这里做一个大体的汇总,废话不多说,直接开始
给出方案,不是手把手..若是连问题和解决都看不懂的..应该去补充下基础知识

问题汇总

Q:安装超时(install timeout)

方案有这么些:

  • cnpm : 国内对npm的镜像版本
/*
cnpm website: https://npm.taobao.org/
*/

npm install -g cnpm --registry=https://registry.npm.taobao.org


// cnpm 的大多命令跟 npm 的是一致的,比如安装,卸载这些

yarn 和 npm 改源大法

使用 nrm 模块 : www.npmjs.com/package/nrm
npm config : npm config set registry https://registry.npm.taobao.org
yarn config : yarn config set registry https://registry.npm.taobao.org

更多内容请访问 https://juejin.im/post/59fa9257f265da43062a1b0e

这篇文章汇总了大量新手在群里的提问,回答非常全面,建议新手阅读。涉及版权问题,没有全文转载。

作者:FungLeo 发表于2017/11/8 17:55:58 原文链接
阅读:43 评论:0 查看评论

react native学习笔记16——存储篇(1)AsyncStorage

$
0
0

前言

React Native中常用的数据本地化存储的方法有如下几种:

  • AsyncStorage:以键值对的形式存储的轻量存储器,只能存储字符串数据。
  • SQLite:一种轻型的数据库,多用于移动端开发,原生应用开发中比较常见。
  • Realm:新兴的移动端数据存储方法,使用简单、跨平台、性能优越功能强大。

本文主要主要介绍第一种——AsyncStorage。

AsyncStorage介绍

AsyncStorage是react native提供的轻量存储器,类似Android中的SharePreference, iOS中的NSUserDefault,以键值对的形式存储将信息存储在本地,它是一个简单的、未加密的、异步的、持久化的Key-Value 存储系统,对于App来说是全局性的。官方建议使用时在AsyncStorage的基础上做一层抽象封装,而不是直接使用AsyncStorage。
AsyncStorage的JS代码是对原生实现的一个简易封装,提供一个更清晰的JS API、返回真正的错误对象,以及简单的单项对象操作函数。每个方法都会返回一个Promise对象。

下面是使用AsyncStorage存储数据、获取数据的简单示例。
存储数据:

try {
  await AsyncStorage.setItem('samplekey', 'lalala.');
} catch (error) {
  // Error saving data
}

获取数据:

try {
  const value = await AsyncStorage.getItem('samplekey');
  if (value !== null){
    // We have data!!
    console.log(value);
  }
} catch (error) {
  // Error retrieving data
}

常用的方法

  1. 获取键所对应的值:static getItem(key: string, callback:(error, result))
    根据键来获取值,获取的结果会放在回调函数中。

  2. 设置键-值对:static setItem(key: string, value: string, callback:(error))
    根据键来设置值。

  3. 移除键-值对:static removeItem(key: string, callback:(error))
    根据键来移除项。

  4. 合并键-值对:static mergeItem(key: string, value: string, callback:(error))
    合并现有值和输入值。

  5. 清除所有的数据:static clear(callback:(error))
    清除所有的内容。

  6. 获取所有的键:static getAllKeys(callback:(error, keys))
    获取所有的键。

  7. 清除进行中的查询操作:static flushGetRequests()
    清除所有进行中的查询操作。

  8. 获取多个键对应的值:static multiGet(keys, callback:(errors, result))
    获取多项,其中 keys 是字符串数组,比如:['k1', 'k2']

  9. 设置多项键-值对:static multiSet(keyValuePairs, callback:(errors))
    设置多项,其中 keyValuePairs 是字符串的二维数组,比如:[['k1', 'val1'], ['k2', 'val2']]

  10. 删除多项键-值对static multiRemove(keys, callback:(errors))
    删除多项,其中 keys 是字符串数组,比如:['k1', 'k2']

  11. 合并多项键-值对static multiMerge(keyValuePairs, callback:(errors))
    多个键值合并,其中 keyValuePairs 是字符串的二维数组,比如:[['k1', 'val1'], ['k2', 'val2']]

使用实例

下面示例中主要通过 AsyncStorage.getItemAsyncStorage.setItemAsyncStorage.removeItem实现AsyncStorage的增删查,如果对同一个key用setItem设置不同的值,后一次设置的值会覆盖前一次的值,以此来实现对数据的修改更新。

import React, { Component } from 'react';
import {
    StyleSheet,
    View,
    Text,
    Dimensions,
    Button,
    AsyncStorage
} from 'react-native';

const {width, height} = Dimensions.get('window');
var data = "";
const AsyncStorageKey = "ASE_";
export default class AsyncStorageExample extends Component {

    constructor(props) {
        super(props);
        this.state = {
            data:""
        };
    }

    setData(text){
        AsyncStorage.setItem(AsyncStorageKey,text,()=>{
            this.setState({
                data:text,
            });
        });
    }

    delData(){
        AsyncStorage.getItem(AsyncStorageKey,(error,text)=>{
            if(text=== null ){
                alert(AsyncStorageKey +"没有对应的值");
            }else{
                //移除键-值对
                AsyncStorage.removeItem(AsyncStorageKey,()=>{
                    this.setState({
                        data:"",
                    },()=>{
                        alert('删除成功');
                    });
                });
            }
        });
    }

    render() {
        return (
            <View style={styles.container}>
                <Button onPress={this.setData.bind(this,"我用 AsyncStorage存了一条信息")} style={styles.itemView} title="存储数据">
                </Button>

                <Button onPress={this.delData.bind(this)} style={styles.itemView} title="删除数据" >
                </Button>

                <Text style={{paddingTop:40}}>
                    AsyncStorage存储的值是:
                </Text>
                <Text>
                    {this.state.data}
                </Text>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#f2f2f2',
        paddingTop:20,
    },
    itemView:{
        backgroundColor:'grey',
        height:44,
        width:width,
        justifyContent:'center',
        marginTop:10,
    },
    itemText:{
        fontSize:15,
        color:'#ffffff',
        textAlign:'left',
        marginLeft:20,
    },
});
作者:teagreen_red 发表于2017/11/8 19:18:04 原文链接
阅读:0 评论:0 查看评论

leetcode题解-125. Valid Palindrome && 680. Valid Palindrome II

$
0
0

这是两道判断字符串是否为回文的题目,先来看第一道:

Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases.

For example,
"A man, a plan, a canal: Panama" is a palindrome.
"race a car" is not a palindrome.

Note:
Have you consider that the string might be empty? This is a good question to ask during an interview.

For the purpose of this problem, we define empty string as valid palindrome.

本题是对于一个字符串(可能含有各种符号)判断其中的字母和数字是否可以构成回文字符串,不考虑大小写之间的区别。题目很简单,使用java内置的函数即可判断一个字符是否为字母或者数字,如果不是的话接着判断下一个字符即可:

    //45%
    public static boolean isPalindrome(String s) {
        s = s.toLowerCase();
        char [] res = s.toCharArray();
        int i=0, j=res.length-1;
        while(i <= j){
            //不是字母或者数字则遍历下一个
            while(i<=j && !Character.isLetterOrDigit(res[i])) i++;
            while(i<=j && !Character.isLetterOrDigit(res[j])) j--;
            if(res[i] != res[j])
                return false;
            else{
                i++;
                j--;
            }
        }
        return true;
    }

此外,我们还可以使用正则表达式的方法来解决,但是效率比较低只能击败8%的用户:

    //8.8%
    public boolean isPalindrome1(String s) {
        String actual = s.replaceAll("[^A-Za-z0-9]", "").toLowerCase();
        String rev = new StringBuffer(actual).reverse().toString();
        return actual.equals(rev);
    }

然后还在discuss中看到了一个效率很高的代码,思想都是一样的,不过这里作者使用数组来实现判断一个字符是否为字母或者数字,效率很高,可以击败99。5%的用户,代码如下所示:

    //99.5%
    private static final char[]charMap = new char[256];
    static{
        for(int i=0;i<10;i++){
            charMap[i+'0'] = (char)(1+i);  // numeric
        }
        for(int i=0;i<26;i++){
            charMap[i+'a'] = charMap[i+'A'] = (char)(11+i);  //alphabetic, ignore cases
        }
    }

    public boolean isPalindrome2(String s) {
        char[]pChars = s.toCharArray();
        int start = 0,end=pChars.length-1;
        char cS,cE;
        while(start<end){
            cS = charMap[pChars[start]];
            cE = charMap[pChars[end]];
            //如果不是字母或者数字则返回0
            if(cS!=0 && cE!=0){
                if(cS!=cE)return false;
                start++;
                end--;
            }else{
                if(cS==0)start++;
                if(cE==0)end--;
            }
        }
        return true;
    }

接下来看第二道题目:

Given a non-empty string s, you may delete at most one character. Judge whether you can make it a palindrome.

Example 1:
Input: "aba"
Output: True
Example 2:
Input: "abca"
Output: True
Explanation: You could delete the character 'c'.
Note:
The string will only contain lowercase characters a-z. The maximum length of the string is 50000.

本题是看资格只有小写字母的字符串中去掉任意一个字符是否可以成为回文字符串,方法也比较简单,就是逐个判断,当出现不想等到时候就尝试删除两个中的一个,然后在判断剩下的字符串是否可以形成回文字符串即可。代码如此下所示:

    public boolean validPalindrome1(String s) {
        char[] res = s.toCharArray();
        int l = -1, r = s.length();
        while (++l < --r)
            if (res[l] != res[r]) return isPalindromic(res, l, r+1) || isPalindromic(res, l-1, r);
        return true;
    }

    public boolean isPalindromic(char[] res, int l, int r) {
        while (++l < --r)
            if (res[l] != res[r]) return false;
        return true;
    }
作者:liuchonge 发表于2017/11/8 19:21:29 原文链接
阅读:0 评论:0 查看评论

NOIP前 基础数学模板

$
0
0
/*
	created by scarlyw
	基础数学模板 
*/
#include <bits/stdc++.h>

//最大公约数 & 最小公倍数 
namespace gcd_lcm() {
//最大公约数 O(logn)
inline int gcd(int a, int b) {
	return b ? gcd(b, a % b) : a;
}

//最小公倍数 (logn)
inline int lcm(int a, int b) {
	return a * b / gcd(a, b);
}
}

//线筛 
namespace s_seive {

//线筛质数 和 莫比乌斯函数(容斥函数) O(n)
int prime[MAXN], miu[MAXN]; 
int prime_cnt, n;
bool not_prime[MAXN];
inline void seive(int n) {
	not_prime[1] = true, miu[1] = 1;
	for (int i = 2; i <= n; ++i) {
		if (!not_prime[i]) prime[++prime_cnt] = i, miu[i] = -1;
		for (int j = 1; j <= prime_cnt && prime[j] * i <= n; ++j) {
			not_prime[i * prime[j]] = true;
			if (i % prime[j] == 0) {
				miu[i * prime[j]] = 0;
				break;
			}
			miu[i * prime[j]] = -miu[i];
		}
	}
}
}

//质因子分解
namespace compose {
	
//暴力分解 O(sqrt(n))
inline void get_pactor() {
	std::cin >> n, std::cout << n << "=";
	for (int i = 2; i <= sqrt(n); ++i) {
		while (n % i == 0) {
			if (n == i) std::cout << i, exit(0);
			else std::cout << i << "*", n /= i;
		}
	}
	if (n != 1) std::cout << n;
}
}

//扩展欧几里得 
namespace extand_gcd() {

//扩欧 O(logn)最后的(x % b + b) % b是满足ax = 1(mod b)的最小x 
long long a, b, x, y, temp;
inline void ext_gcd(long long a, long long b) {
	if (b == 0) x = 1, y = 0;
	else ext_gcd(b, a % b), temp = x, x = y, y = temp - a / b * y; 
}
}

//快速幂 & 快速乘 
namespace mul_pow {

//O(log n)快速乘 
long long n, m, mod;
inline long long mod_mul(long long a, long long b) {
	long long ans = 0;
	for (; b; b >>= 1, a = (a + a) % mod)
		if (b & 1) ans = (ans + a) % mod;
	return ans;
}

//O(1)快速乘 
inline long long mod_mul(long long a, long long b) {
	return (a * b - (long long)((long double)a / mod * b) * mod + mod) % mod;
}

//O(logn)快速幂 
inline long long mod_pow(long long a, long long b) {
	long long ans = 1;
	for (; b; b >>= 1, a = mod_mul(a, a))
		if (b & 1) ans = mod_mul(ans, a);
	return ans;
}
}

//组合数 
namespace combination {

//O(n ^ 2)组合数递推,方便取模 
const int MAXN = 1000 + 10;
const int mod = 1000000000 + 7;
int c[MAXN][MAXN];
inline void get_c(int n) {
	for (int i = 0; i <= n; ++i) c[i][0] = 1, c[i][i] = 1;
	for (int i = 2; i <= n; ++i)
		for (int j = 1; j < i; ++j)
			c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}

//O(n)预处理阶乘和阶乘逆元,然后O(1)询问组合数 
int fac[MAXN], inv[MAXN];
inline void get_c(int n) {
	fac[0] = 1;
	for (int i = 1; i <= n; ++i) fac[i] = (long long)fac[i - 1] * i % mod;
	inv[n] = mod_pow(fac[n], mod - 2);
	for (int i = n - 1; i >= 0; --i) 
		inv[i] = (long long)inv[i + 1] * (i + 1) % mod;
}

inline int c(int n, int m) {
	return (long long)fac[n] * inv[m] % mod * inv[n - m] % mod;
}
}

//进制转换 
namespace radix_change {

//十进制转任意进制 
inline void radix_10_to_a(int num, int radix) {
	static int sum[100], cnt;
	while (num) sum[++cnt] = num % radix, num /= radix;
	for (int i = cnt; i >= 1; --i) 
		if (sum[i] >= 10) std::cout << (char)(sum[i] + 'A' - 10);
	else std::cout << sum[i];
	return 0;
}

//任意进制转十进制 
inline void radix_a_to_10(int radix, char *s) {
	static int cnt, y;
	cnt = strlen(s + 1);
	for (int i = 1, y = 0; i <= cnt; ++i) {
		if (isdigit(s[i])) y = y * radix + s[i] - '0';
		else y = y * radix + s[i] - 'A' + 10;
	}
	std::cout << y;
}
}

//高精度 
namespace big_integer {

 
const int MAXN = 200 + 10;
const int DIGIT = 1000000000;
//压9位的简单高精度类 
struct big_integer {
	long long a[MAXN];
	int len;
	
	//将big_integer初始化为long long or string代表的数字 
	big_integer(long long s = 0) {
		len = 0, memset(a, 0, sizeof(a));
		do a[++len] = s % DIGIT, s /= DIGIT;
		while (s);
	}
	
	big_integer(char *s) {
		len = 0, memset(a, 0, sizeof(a));
		int s_len = strlen(s + 1);
		long long t = 0, k = 1;
		for (int i = s_len; i >= 1; --i) {
			t = t + (s[i] ^ '0') * k, k = ((k << 2) + k << 1);
			if (k == DIGIT) a[++len] = t, k = 1, t = 0;
		}
		if (t != 0) a[++len] = t;
	}
	
	//重载方括号b[i] = b.a[i]; 
	inline long long & operator [] (const int &c) {
		return a[c];
	}
	
	//重载方括号b[i] = b.a[i]; 
	inline const long long & operator [] (const int &c) const {
		return a[c];
	}
	
	//重载 <  
	inline bool operator < (const big_integer &b) const {
		if (len != b.len) return len < b.len;
		for (int i = len; i >= 1; --i) 
			if (a[i] != b[i]) return a[i] < b[i];
		return false;
	}
	
	//高精 + 高精,重载 +,返回高精和 
	inline big_integer operator + (const big_integer &b) const {
		big_integer c;
		for (int i = 1; i <= len || i <= b.len; ++i)
			c[i] += a[i] + b[i], c[i + 1] += c[i] / DIGIT, c[i] %= DIGIT;
		c.len = std::max(len, b.len) + 2;
		while (c[c.len] == 0) c.len--;
		return (c.len) ? (0) : (c.len = 0), c;
	}
	
	//高精 * 高精,重载 *,返回高精积 
	inline big_integer operator * (const big_integer &b) const {
		big_integer c;
		for (int i = 1; i <= len; ++i)
			for (int j = 1; j <= b.len; ++j) {
				int pos = i + j - 1;
				c[pos] += a[i] * b[j], c[pos + 1] += c[pos] / DIGIT;
				c[pos] %= DIGIT;
			}
		c.len = len + b.len + 3;
		while (c[c.len] == 0) c.len--;
		return (c.len) ? (0) : (c.len = 1), c;
	}
	
	//高精 - 高精,重载 -,返回高精差,默认减数 > 被减数 
	inline big_integer operator - (const big_integer &b) const {
		big_integer c;
		for (int i = 1; i <= len; ++i)
			c[i] += a[i] - b[i], (c[i] < 0) ? (c[i] += DIGIT, c[i + 1]--) : 0;
		c.len = len;
		while (c[c.len] == 0) c.len--;
		return (c.len) ? (0) : (c.len = 1), c;
	}
	
	//高精 / 单精,重载 /,返回高精商 
	inline big_integer operator / (const long long b) const {
		big_integer c;
		long long t = 0;
		for (int i = len; i >= 1; --i)
			c[i] = (a[i] + t * DIGIT) / b, t = (a[i] + t * DIGIT) % b;
		c.len = len;
		while (c[c.len] == 0) c.len--;
		return (c.len) ? (0) : (c.len = 1), c;
	}
	
	//高精 % 单精, 重载%,返回单精 
	inline big_integer operator % (const long long b) const {
		return *this - *this / b * b;
	}
	
	//输出 
	inline void write() const {
		printf("%lld", a[len]);
		for (int i = len - 1; i >= 1; --i) printf("%09lld", a[i]);
	}
} ;

//重载cin, cin >> (big_integer)a, 直接读入一个字符串,然后将a赋值为字符串 
inline std::istream & operator >> (std::istream &a, big_integer &b) {
	static char s[MAXN << 4];
	scanf("%s", s + 1), b = s;
	return a;
}

//重载cout, cout << (big_integer)b, 输出b,利用write() 
inline std::ostream & operator << (std::ostream &a, const big_integer &b) {
	b.write();
	return a;
}
}

//高斯消元
namespace gauss {

const int MAXN = 100 + 10;
int n;
double f[MAXN][MAXN], ans[MAXN];
//f[i][j]表示第i个等式中第j个变量的系数,f[i][n + 1]表示第i个等式的值 
inline void solve() {
	for (int i = 1; i <= n; ++i) {
		int pos = -1;
		for (int j = i; j <= n; ++j) 
			if (f[j][i] != 0) {
				pos = j;
				break ;
			}
		std::swap(f[pos], f[i]);
		for (int j = i + 1; j <= n; ++j) {
			double t = f[j][i] / f[i][i];
			for (int k = i; k <= n + 1; ++k)
				f[j][k] -= t * f[i][k];
		}
	}
	for (int i = n; i >= 1; --i) {
		ans[i] = f[i][n + 1] / f[i][i];
		for (int j = 1; j < i; ++j)
			f[j][n + 1] -= f[j][i] * ans[i];
	}
	for (int i = 1; i <= n; ++i) std::cout << int(ans[i] + 0.5) << " ";
}
}

//矩阵乘法 & 矩阵快速幂
namespace matrix {

const int MAXN = 200 + 10;
const int mod = 1000000000 + 7;
//矩阵类 
struct matrix {
	int a[MAXN][MAXN];
	int n;
	matrix() {}
	matrix(int n) : n(n) {
		for (int i = 1; i <= n; ++i)
			for (int j = 1; j <= n; ++j)
				a[i][j] = 0;
	}
	
	//重载 * , 矩阵乘法 
	inline matrix operator * (const matrix &b) const {
		matrix ans(n);
		for (int i = 1; i <= n; ++i)
			for (int k = 1; k <= n; ++k)
				for (int j = 1; j <= n; ++j)
					ans.a[i][j] = (ans.a[i][j] + a[i][k] * b.a[k][j]) % mod;
		return ans;
	}
	
	//重载 ^ ,矩阵快速幂 
	inline matrix operator ^ (int b) const {
		matrix ans(n), a = *this;
		for (int i = 1; i <= n; ++i) ans.a[i][i] = 1;
		for (; b; b >>= 1, a = a * a) 
			if (b & 1) ans = ans * a;
		return ans;
	}
};
}

作者:scar_lyw 发表于2017/11/8 19:05:31 原文链接
阅读:5 评论:0 查看评论

LeetCode--Reverse Linked List II

$
0
0

Reverse a linked list from position m to n. Do it in-place and in one-pass.

For example:
Given 1->2->3->4->5->NULL, m = 2 and n = 4,

return 1->4->3->2->5->NULL.

Note:
Given m, n satisfy the following condition:
1 ≤ m ≤ n ≤ length of list.

思路:头插法。这是一个翻转链表题目的扩展,在头插法的基础上考虑在一个区间内反转。那么,首先要找到prev指针,然后确定3个指针head2,prev,cur的位置,头插法把cur指针内容插到head2后面,然后更新cur指针。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        ListNode dummy(0);
        dummy.next=head;
        ListNode *prev=&dummy;
        for(int i=0;i<m-1;i++){
            prev=prev->next;
        }
        ListNode *head2=prev;
        prev=head2->next;
        ListNode *cur=prev->next;
        for(int i=m;i<n;i++){
            prev->next=cur->next;
            cur->next=head2->next;
            head2->next=cur;
            cur=prev->next;
        }
        return dummy.next;
    }
};
作者:qq_20791919 发表于2017/11/8 18:22:03 原文链接
阅读:4 评论:0 查看评论

知识板块梳理

$
0
0

数论

  • gcd
  • Exgcd
  • Lucas定理
  • 扩展Lucas
  • 中国剩余定理
  • 扩展中国剩余定理
  • 排列组合(杨辉三角递推式)
  • 卡特兰数h(n) = C(2n,n-1)/n
  • 错排D(n)=(n-1)(D(n-1)+D(n-2))
  • BSGS
  • 高斯消元
  • 线性基
  • 快速幂
  • 矩阵快速幂
  • 素数筛(欧拉函数, 莫比乌斯函数,约数个数函数, 各种积性函数)
  • 莫比乌斯反演

图论

  • 割点
  • 强连通分量
  • 点双连通分量
  • dfs序
  • 倍增LCA
  • 欧拉回路
  • 括号序
  • 树链剖分
  • 最小生成树(Kruskal, Prim)
  • 最短路径树
  • 最短路(dijkstra,floyd,SPFA)
  • 差分约束系统
  • 二分图(染色)
  • 匈牙利算法
  • 网络流
  • 树的重心/直径
  • 树分治(点分,边分)
  • 拓扑排序

数据结构

  • 队列(双端队列)
  • 链表
  • ST表
  • 堆( 手写堆,优先队列)
  • 平衡树(set,map,Treap ,Splay,各种树)
  • 线段树
  • 树状数组
  • 可并堆( 左偏树)
  • 主席树
  • 树套树( 套树^n,n≥2)
  • 分块

博弈论

  • NIM游戏
  • 胜负局面图
  • SG函数,anti-SG
  • alpha-beta 对抗搜索

搜索

  • BFS
  • DFS
  • 各种剪枝,分支定界(if(now>ans) return; )
  • 换顺序搜(倒着搜,斜着搜,随便乱搜)
  • A*
  • IDDFS
  • alpha-beta 对抗搜索

字符串

  • KMP
  • Hash
  • manacher
  • trie树
  • AC自动机
  • 后缀数组

DP

  • 背包DP
  • 区间DP
  • 状压DP
  • 数位DP
  • 树形DP
  • 期望DP
  • 瞎JBDP

其他

  • 二分答案
  • Hash
  • CDQ
  • 贪心
  • 模拟
  • 扫描线
  • 李超线段树
  • 分块
  • 莫队
  • 差分(比如:询问[L,R]转化为[1,R]-[1,L],树上差分)
  • 前缀和(数组前缀和,树上到根前缀和)
  • 离散化
  • 读入优化
作者:Izumi_Hanako 发表于2017/11/8 19:36:55 原文链接
阅读:10 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>